Implement animations, pipeline for duplicates and merging.
This commit is contained in:
+81
-52
@@ -11,13 +11,29 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="4222a6b8-add3-4f43-9c42-fd6bb5a76169" name="Changes" comment="">
|
<list default="true" id="4222a6b8-add3-4f43-9c42-fd6bb5a76169" name="Changes" comment="">
|
||||||
|
<change afterPath="$PROJECT_DIR$/Assets/Resources/TerminalScrollbars.uss" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/Assets/Scripts/Meta/DistroVersionCatalog.cs" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/Assets/Scripts/Meta/GachaTuning.cs" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/Assets/Scripts/Meta/PullResolver.cs" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/Assets/Scripts/Meta/VersionUpgrader.cs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/CombatTuning.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/CombatTuning.cs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Assets/Resources/CombatScene.uss" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Resources/CombatScene.uss" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Meta/GachaService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Meta/GachaService.cs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/CardEffects.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/CardEffects.cs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Run/RunManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Run/RunManager.cs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/CombatManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/CombatManager.cs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/CombatantState.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/CombatantState.cs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/DamagePipeline.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/DamagePipeline.cs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/DeckController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/DeckController.cs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Meta/PlayerCollection.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Meta/PlayerCollection.cs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Meta/SaveService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Meta/SaveService.cs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Run/RunConfig.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Run/RunConfig.cs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Run/RunContext.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Run/RunContext.cs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/UI/CollectionScreenController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/CollectionScreenController.cs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/UI/CombatSceneController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/CombatSceneController.cs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/UI/CombatSceneController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/CombatSceneController.cs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/UI/GachaScreenController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/GachaScreenController.cs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/UI/GachaScreenController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/GachaScreenController.cs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/UI/MainMenuController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/MainMenuController.cs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Assets/Scripts/UI/MainMenuController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/MainMenuController.cs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Assets/UI/CollectionScreen.uxml" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/UI/CollectionScreen.uxml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Assets/UI/MainMenu.uss" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/UI/MainMenu.uss" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Assets/UI/MainMenu.uxml" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/UI/MainMenu.uxml" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -52,9 +68,13 @@
|
|||||||
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Fedora/CargoBuildRelease.asset" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Fedora/CargoBuildRelease.asset" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/Python/PyPrint.asset" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/Python/PyPrint.asset" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Ubuntu/AskUbuntu.asset" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Ubuntu/AskUbuntu.asset" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="file://$PROJECT_DIR$/Assets/Data/Distros/DistroDatabase.asset" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$PROJECT_DIR$/Assets/Data/Distros/Fedora.asset" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$PROJECT_DIR$/Assets/Data/Distros/Fedora.asset" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$PROJECT_DIR$/Assets/Data/Distros/Mint.asset" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$PROJECT_DIR$/Assets/Data/Distros/Mint.asset" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$PROJECT_DIR$/Assets/Data/Distros/Ubuntu.asset" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$PROJECT_DIR$/Assets/Data/Distros/Ubuntu.asset" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="file://$PROJECT_DIR$/Assets/Data/Passives/AptUpdate.asset" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="file://$PROJECT_DIR$/Assets/Data/Passives/BleedingEdge.asset" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="file://$PROJECT_DIR$/Assets/Data/Passives/ItJustWorks.asset" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$PROJECT_DIR$/Assets/Scripts/Data/LanguageDeckDatabase.cs" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$PROJECT_DIR$/Assets/Scripts/Data/LanguageDeckDatabase.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$PROJECT_DIR$/Assets/Scripts/UI.meta" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$PROJECT_DIR$/Assets/Scripts/UI.meta" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$PROJECT_DIR$/Assets/UI/MainMenuPanelSettings.asset" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$PROJECT_DIR$/Assets/UI/MainMenuPanelSettings.asset" root0="FORCE_HIGHLIGHTING" />
|
||||||
@@ -70,23 +90,23 @@
|
|||||||
<component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" />
|
<component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" />
|
||||||
<component name="NextEditCompletionFeaturesState">
|
<component name="NextEditCompletionFeaturesState">
|
||||||
<decayedCancelled>
|
<decayedCancelled>
|
||||||
<entry key="MS100" value="1.6593257874423712" />
|
<entry key="MS100" value="1.42846046364743" />
|
||||||
<entry key="MS500" value="4.690994084349008" />
|
<entry key="MS500" value="3.1473272504526837" />
|
||||||
<entry key="S2" value="14.465357308970995" />
|
<entry key="S2" value="4.406032316795342" />
|
||||||
<entry key="S5" value="30.992381303472097" />
|
<entry key="S5" value="7.013900503844766" />
|
||||||
<entry key="S10" value="48.5846958827385" />
|
<entry key="S10" value="16.21402635518147" />
|
||||||
<entry key="S30" value="71.76833898022443" />
|
<entry key="S30" value="38.90401123450886" />
|
||||||
<entry key="S60" value="80.15161397083796" />
|
<entry key="S60" value="52.53282462788902" />
|
||||||
<entry key="M2" value="84.87531446244894" />
|
<entry key="M2" value="76.41615227872627" />
|
||||||
<entry key="M5" value="87.90107930582121" />
|
<entry key="M5" value="125.46422729844767" />
|
||||||
<entry key="M10" value="89.36799351143095" />
|
<entry key="M10" value="158.76574582237612" />
|
||||||
<entry key="M15" value="93.16275126446527" />
|
<entry key="M15" value="176.22359481016986" />
|
||||||
<entry key="M30" value="133.51634266577747" />
|
<entry key="M30" value="217.72508392110834" />
|
||||||
<entry key="H1" value="278.73000832894314" />
|
<entry key="H1" value="328.4135925429288" />
|
||||||
<entry key="H2" value="556.4819305444627" />
|
<entry key="H2" value="583.9663964386095" />
|
||||||
<entry key="H4" value="958.972222388022" />
|
<entry key="H4" value="1000.6154353482859" />
|
||||||
<entry key="D1" value="2009.2210985288775" />
|
<entry key="D1" value="2163.2750640427894" />
|
||||||
<entry key="W1" value="2381.8392808733556" />
|
<entry key="W1" value="2591.3632929927458" />
|
||||||
</decayedCancelled>
|
</decayedCancelled>
|
||||||
<decayedSelected>
|
<decayedSelected>
|
||||||
<entry key="MS100" value="0.0" />
|
<entry key="MS100" value="0.0" />
|
||||||
@@ -94,37 +114,37 @@
|
|||||||
<entry key="S2" value="0.0" />
|
<entry key="S2" value="0.0" />
|
||||||
<entry key="S5" value="0.0" />
|
<entry key="S5" value="0.0" />
|
||||||
<entry key="S10" value="0.0" />
|
<entry key="S10" value="0.0" />
|
||||||
<entry key="S30" value="3.081765536046895E-110" />
|
<entry key="S30" value="4.679335776173224E-152" />
|
||||||
<entry key="S60" value="1.7554958091795295E-55" />
|
<entry key="S60" value="2.163177241044572E-76" />
|
||||||
<entry key="M2" value="4.189863731888548E-28" />
|
<entry key="M2" value="1.4707743678227887E-38" />
|
||||||
<entry key="M5" value="1.1191285094720122E-11" />
|
<entry key="M5" value="7.362383250718034E-16" />
|
||||||
<entry key="M10" value="3.3453378147386345E-6" />
|
<entry key="M10" value="2.713371196633097E-8" />
|
||||||
<entry key="M15" value="2.2367974199991652E-4" />
|
<entry key="M15" value="9.029689298894987E-6" />
|
||||||
<entry key="M30" value="0.014955931470204738" />
|
<entry key="M30" value="0.0030049451106002496" />
|
||||||
<entry key="H1" value="0.12241465504527704" />
|
<entry key="H1" value="0.05487126235349662" />
|
||||||
<entry key="H2" value="0.36869769063784513" />
|
<entry key="H2" value="0.24684605257568554" />
|
||||||
<entry key="H4" value="0.8300520405421339" />
|
<entry key="H4" value="0.6791773688385119" />
|
||||||
<entry key="D1" value="2.883634496098765" />
|
<entry key="D1" value="2.788815988774459" />
|
||||||
<entry key="W1" value="3.8121106294007556" />
|
<entry key="W1" value="3.7939461251078934" />
|
||||||
</decayedSelected>
|
</decayedSelected>
|
||||||
<decayedShown>
|
<decayedShown>
|
||||||
<entry key="MS100" value="1.6056852423710548" />
|
<entry key="MS100" value="1.36580045961104" />
|
||||||
<entry key="MS500" value="4.655592836166669" />
|
<entry key="MS500" value="3.120980742316745" />
|
||||||
<entry key="S2" value="14.436328275205241" />
|
<entry key="S2" value="4.3968676938883835" />
|
||||||
<entry key="S5" value="30.967614820111674" />
|
<entry key="S5" value="7.008354689811898" />
|
||||||
<entry key="S10" value="48.565252560398456" />
|
<entry key="S10" value="16.20802261033003" />
|
||||||
<entry key="S30" value="71.75871155917086" />
|
<entry key="S30" value="38.89936714660714" />
|
||||||
<entry key="S60" value="80.14622622688532" />
|
<entry key="S60" value="52.52966542050411" />
|
||||||
<entry key="M2" value="84.872458348968" />
|
<entry key="M2" value="76.41373653537866" />
|
||||||
<entry key="M5" value="87.89989521451953" />
|
<entry key="M5" value="125.46254176247474" />
|
||||||
<entry key="M10" value="89.36739411138522" />
|
<entry key="M10" value="158.76465401645888" />
|
||||||
<entry key="M15" value="93.16255011580246" />
|
<entry key="M15" value="176.22278439428334" />
|
||||||
<entry key="M30" value="133.530877068796" />
|
<entry key="M30" value="217.72754364072628" />
|
||||||
<entry key="H1" value="278.8516097409327" />
|
<entry key="H1" value="328.467843705019" />
|
||||||
<entry key="H2" value="556.8495610944027" />
|
<entry key="H2" value="584.2123911751798" />
|
||||||
<entry key="H4" value="959.8012838755719" />
|
<entry key="H4" value="1001.293731005342" />
|
||||||
<entry key="D1" value="2012.1043621012425" />
|
<entry key="D1" value="2166.063508996286" />
|
||||||
<entry key="W1" value="2385.651327343784" />
|
<entry key="W1" value="2595.1571734947097" />
|
||||||
</decayedShown>
|
</decayedShown>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectColorInfo">{
|
<component name="ProjectColorInfo">{
|
||||||
@@ -213,7 +233,7 @@
|
|||||||
<workItem from="1783330590375" duration="18451000" />
|
<workItem from="1783330590375" duration="18451000" />
|
||||||
<workItem from="1783371975450" duration="26000" />
|
<workItem from="1783371975450" duration="26000" />
|
||||||
<workItem from="1783372397891" duration="989000" />
|
<workItem from="1783372397891" duration="989000" />
|
||||||
<workItem from="1783410502976" duration="5821000" />
|
<workItem from="1783410502976" duration="10247000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="implement main screen">
|
<task id="LOCAL-00001" summary="implement main screen">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -335,7 +355,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1783417593908</updated>
|
<updated>1783417593908</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="16" />
|
<task id="LOCAL-00016" summary="implement scaling rewards per wave completed in combat mode">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1783421764662</created>
|
||||||
|
<option name="number" value="00016" />
|
||||||
|
<option name="presentableId" value="LOCAL-00016" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1783421764662</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="17" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -365,7 +393,8 @@
|
|||||||
<MESSAGE value="finish implementing main" />
|
<MESSAGE value="finish implementing main" />
|
||||||
<MESSAGE value="fix some stuff" />
|
<MESSAGE value="fix some stuff" />
|
||||||
<MESSAGE value="Implement too many changes to list" />
|
<MESSAGE value="Implement too many changes to list" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="Implement too many changes to list" />
|
<MESSAGE value="implement scaling rewards per wave completed in combat mode" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="implement scaling rewards per wave completed in combat mode" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XDebuggerManager">
|
<component name="XDebuggerManager">
|
||||||
<breakpoint-manager>
|
<breakpoint-manager>
|
||||||
|
|||||||
@@ -32,6 +32,10 @@
|
|||||||
--kp-lang-go: #6de6ff;
|
--kp-lang-go: #6de6ff;
|
||||||
--kp-lang-ruby: #ff5f7d;
|
--kp-lang-ruby: #ff5f7d;
|
||||||
--kp-lang-php: #9aa2ff;
|
--kp-lang-php: #9aa2ff;
|
||||||
|
--kp-primary: var(--kp-text);
|
||||||
|
--kp-scroll-track: var(--kp-panel);
|
||||||
|
--kp-scroll-thumb: var(--kp-text-dim);
|
||||||
|
--kp-scroll-thumb-hover: var(--kp-border-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.combat-root {
|
.combat-root {
|
||||||
@@ -186,6 +190,8 @@
|
|||||||
.meter-fill {
|
.meter-fill {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: var(--kp-text);
|
background-color: var(--kp-text);
|
||||||
|
transition-property: width, background-color;
|
||||||
|
transition-duration: 220ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meter-fill-warning {
|
.meter-fill-warning {
|
||||||
@@ -951,3 +957,200 @@
|
|||||||
opacity: 0.72;
|
opacity: 0.72;
|
||||||
border-color: var(--kp-text-dim);
|
border-color: var(--kp-text-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.feedback-layer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-vignette {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
opacity: 0;
|
||||||
|
border-left-width: 10px;
|
||||||
|
border-right-width: 10px;
|
||||||
|
border-top-width: 10px;
|
||||||
|
border-bottom-width: 10px;
|
||||||
|
border-color: var(--kp-danger);
|
||||||
|
transition-property: opacity;
|
||||||
|
transition-duration: 120ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-vignette-on {
|
||||||
|
opacity: 0.62;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-number {
|
||||||
|
position: absolute;
|
||||||
|
min-width: 60px;
|
||||||
|
height: 24px;
|
||||||
|
-unity-text-align: middle-center;
|
||||||
|
-unity-font-style: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--kp-warning);
|
||||||
|
transition-property: top, opacity;
|
||||||
|
transition-duration: 420ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-damage {
|
||||||
|
color: var(--kp-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-heal {
|
||||||
|
color: var(--kp-beneficial);
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-status {
|
||||||
|
color: var(--kp-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-muted {
|
||||||
|
color: var(--kp-text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-kill {
|
||||||
|
color: var(--kp-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-large {
|
||||||
|
font-size: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-static {
|
||||||
|
transition-duration: 0ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-card-ghost {
|
||||||
|
opacity: 0.9;
|
||||||
|
transition-property: left, top, opacity;
|
||||||
|
transition-duration: 260ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-card-route {
|
||||||
|
border-color: var(--kp-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-card-strike {
|
||||||
|
border-color: var(--kp-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-card-discard {
|
||||||
|
border-color: var(--kp-text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-card-instant {
|
||||||
|
opacity: 0.72;
|
||||||
|
transition-duration: 0ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-hit {
|
||||||
|
background-color: var(--kp-action-hover);
|
||||||
|
border-color: var(--kp-warning);
|
||||||
|
scale: 1.025 0.98;
|
||||||
|
transition-property: scale, background-color, border-color;
|
||||||
|
transition-duration: 90ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-block {
|
||||||
|
border-color: var(--kp-beneficial);
|
||||||
|
transition-property: border-color;
|
||||||
|
transition-duration: 90ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-status {
|
||||||
|
border-color: var(--kp-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-boost {
|
||||||
|
border-color: var(--kp-beneficial);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-killed {
|
||||||
|
opacity: 0.5;
|
||||||
|
scale: 0.95 0.95;
|
||||||
|
border-color: var(--kp-danger);
|
||||||
|
transition-property: opacity, scale, border-color;
|
||||||
|
transition-duration: 180ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.death-ghost {
|
||||||
|
background-color: var(--kp-panel-raised);
|
||||||
|
border-left-width: 2px;
|
||||||
|
border-right-width: 1px;
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-color: var(--kp-danger);
|
||||||
|
opacity: 0.72;
|
||||||
|
transition-property: opacity, scale;
|
||||||
|
transition-duration: 300ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enemy-acting {
|
||||||
|
border-color: var(--kp-accent);
|
||||||
|
scale: 1.035 1.035;
|
||||||
|
transition-property: scale, border-color;
|
||||||
|
transition-duration: 120ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phase-pulse {
|
||||||
|
background-color: var(--kp-warning);
|
||||||
|
transition-property: background-color;
|
||||||
|
transition-duration: 120ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cycles-spent-beat {
|
||||||
|
opacity: 0.62;
|
||||||
|
transition-property: opacity;
|
||||||
|
transition-duration: 120ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-denied {
|
||||||
|
border-color: var(--kp-danger);
|
||||||
|
translate: 4px 0;
|
||||||
|
transition-property: translate, border-color;
|
||||||
|
transition-duration: 70ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hand-card-drawn {
|
||||||
|
border-color: var(--kp-beneficial);
|
||||||
|
translate: 0 -8px;
|
||||||
|
transition-property: translate, border-color;
|
||||||
|
transition-duration: 160ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-chip-resolve {
|
||||||
|
border-color: var(--kp-warning);
|
||||||
|
scale: 1.06 1.06;
|
||||||
|
transition-property: scale, border-color;
|
||||||
|
transition-duration: 110ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stack-chip-resolve {
|
||||||
|
border-color: var(--kp-lang-haskell);
|
||||||
|
scale: 1.08 1.08;
|
||||||
|
transition-property: scale, border-color;
|
||||||
|
transition-duration: 130ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reduced-motion .float-number,
|
||||||
|
.reduced-motion .feedback-card-ghost,
|
||||||
|
.reduced-motion .damage-vignette,
|
||||||
|
.reduced-motion .feedback-hit,
|
||||||
|
.reduced-motion .feedback-killed,
|
||||||
|
.reduced-motion .enemy-acting,
|
||||||
|
.reduced-motion .phase-pulse,
|
||||||
|
.reduced-motion .cycles-spent-beat,
|
||||||
|
.reduced-motion .feedback-denied,
|
||||||
|
.reduced-motion .hand-card-drawn,
|
||||||
|
.reduced-motion .queue-chip-resolve,
|
||||||
|
.reduced-motion .stack-chip-resolve,
|
||||||
|
.reduced-motion .death-ghost {
|
||||||
|
transition-duration: 0ms;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
:root {
|
||||||
|
--kp-scroll-track: var(--kp-panel);
|
||||||
|
--kp-scroll-thumb: var(--kp-border);
|
||||||
|
--kp-scroll-thumb-hover: var(--kp-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView .unity-scroll-view__content-viewport {
|
||||||
|
padding-right: 2px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView .unity-scroll-view__vertical-scroller {
|
||||||
|
width: 6px;
|
||||||
|
min-width: 6px;
|
||||||
|
background-color: transparent;
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-top-width: 0;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView .unity-scroll-view__horizontal-scroller {
|
||||||
|
height: 6px;
|
||||||
|
min-height: 6px;
|
||||||
|
background-color: transparent;
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-top-width: 0;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView .unity-scroller,
|
||||||
|
ScrollView .unity-scroller--vertical,
|
||||||
|
ScrollView .unity-scroller--horizontal,
|
||||||
|
ScrollView .unity-scroller__slider,
|
||||||
|
ScrollView .unity-base-slider {
|
||||||
|
background-color: transparent;
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-top-width: 0;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView .unity-scroller__low-button,
|
||||||
|
ScrollView .unity-scroller__high-button {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-top-width: 0;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView .unity-base-slider__tracker {
|
||||||
|
background-color: var(--kp-scroll-track);
|
||||||
|
opacity: 0.28;
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-top-width: 0;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView .unity-base-slider__dragger,
|
||||||
|
ScrollView .unity-base-slider__dragger-border,
|
||||||
|
ScrollView #unity-dragger {
|
||||||
|
min-width: 6px;
|
||||||
|
min-height: 6px;
|
||||||
|
background-color: var(--kp-scroll-thumb);
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-top-width: 0;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView .unity-base-slider__dragger:hover,
|
||||||
|
ScrollView .unity-base-slider__dragger-border:hover,
|
||||||
|
ScrollView #unity-dragger:hover {
|
||||||
|
background-color: var(--kp-scroll-thumb-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView .unity-base-slider__dragger:active,
|
||||||
|
ScrollView .unity-base-slider__dragger-border:active,
|
||||||
|
ScrollView #unity-dragger:active {
|
||||||
|
background-color: var(--kp-scroll-thumb-hover);
|
||||||
|
}
|
||||||
@@ -43,8 +43,8 @@ namespace KernelPanic.Combat
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int minAmount = UpgradeMath.ScaleAmount(_minAmount, context.Card);
|
int minAmount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_minAmount, context), context.Card);
|
||||||
int maxAmount = UpgradeMath.ScaleAmount(_maxAmount, context.Card);
|
int maxAmount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_maxAmount, context), context.Card);
|
||||||
int amount = minAmount == maxAmount
|
int amount = minAmount == maxAmount
|
||||||
? minAmount
|
? minAmount
|
||||||
: RandomRoll.RollRange(minAmount, maxAmount, new RollContext(context.Source));
|
: RandomRoll.RollRange(minAmount, maxAmount, new RollContext(context.Source));
|
||||||
@@ -77,7 +77,7 @@ namespace KernelPanic.Combat
|
|||||||
|
|
||||||
public void Execute(CombatContext context)
|
public void Execute(CombatContext context)
|
||||||
{
|
{
|
||||||
int amount = UpgradeMath.ScaleShield(_amount, context.Card);
|
int amount = UpgradeMath.ScaleShield(UpgradeMath.ApplySourceFlatBonus(_amount, context), context.Card);
|
||||||
context.Source.Shield += amount;
|
context.Source.Shield += amount;
|
||||||
context.CombatManager.ReportEffectResult($"gained {amount} shield");
|
context.CombatManager.ReportEffectResult($"gained {amount} shield");
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ namespace KernelPanic.Combat
|
|||||||
}
|
}
|
||||||
|
|
||||||
int roll = RandomRoll.RollRange(1, 100, new RollContext(context.Source));
|
int roll = RandomRoll.RollRange(1, 100, new RollContext(context.Source));
|
||||||
int damageAmount = UpgradeMath.ScaleAmount(_damageAmount, context.Card);
|
int damageAmount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_damageAmount, context), context.Card);
|
||||||
int amount = roll <= _successPercent ? damageAmount : 0;
|
int amount = roll <= _successPercent ? damageAmount : 0;
|
||||||
context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, false, false));
|
context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, false, false));
|
||||||
context.CombatManager.ReportEffectResult(amount == 0 ? "typeof -> NaN (0)" : $"typeof -> {amount}");
|
context.CombatManager.ReportEffectResult(amount == 0 ? "typeof -> NaN (0)" : $"typeof -> {amount}");
|
||||||
@@ -136,13 +136,13 @@ namespace KernelPanic.Combat
|
|||||||
{
|
{
|
||||||
if (_targetSelf)
|
if (_targetSelf)
|
||||||
{
|
{
|
||||||
context.StatusEffects.Apply(context.Source, _statusType, UpgradeMath.ScaleAmount(_stacks, context.Card), _duration, context.Source, _skipNextTick);
|
context.StatusEffects.Apply(context.Source, _statusType, UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_stacks, context), context.Card), _duration, context.Source, _skipNextTick);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < context.Targets.Count; i++)
|
for (int i = 0; i < context.Targets.Count; i++)
|
||||||
{
|
{
|
||||||
context.StatusEffects.Apply(context.Targets[i], _statusType, UpgradeMath.ScaleAmount(_stacks, context.Card), _duration, context.Source, _skipNextTick);
|
context.StatusEffects.Apply(context.Targets[i], _statusType, UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_stacks, context), context.Card), _duration, context.Source, _skipNextTick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +184,15 @@ namespace KernelPanic.Combat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RAM is a hard hand cap. Draw effects only pull cards that can fit; blocked cards stay in the draw pile.
|
// RAM is a hard hand cap. Draw effects only pull cards that can fit; blocked cards stay in the draw pile.
|
||||||
int requested = UpgradeMath.ScaleAmount(_count, context.Card);
|
int baseCount = _count;
|
||||||
|
if (context.Card?.Definition?.Id == "ubuntu_ask_ubuntu"
|
||||||
|
&& string.Equals(context.CombatManager.RunConfig?.Distro?.Id, "ubuntu", System.StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& context.CombatManager.RunConfig.DistroVersion >= 3)
|
||||||
|
{
|
||||||
|
baseCount = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
int requested = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(baseCount, context), context.Card);
|
||||||
int drawCount = UnityEngine.Mathf.Min(requested, room);
|
int drawCount = UnityEngine.Mathf.Min(requested, room);
|
||||||
IReadOnlyList<CardInstance> drawn = context.DeckController.Draw(drawCount);
|
IReadOnlyList<CardInstance> drawn = context.DeckController.Draw(drawCount);
|
||||||
int added = 0;
|
int added = 0;
|
||||||
@@ -214,8 +222,8 @@ namespace KernelPanic.Combat
|
|||||||
|
|
||||||
public void Execute(CombatContext context)
|
public void Execute(CombatContext context)
|
||||||
{
|
{
|
||||||
int minCount = UpgradeMath.ScaleAmount(_minCount, context.Card);
|
int minCount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_minCount, context), context.Card);
|
||||||
int maxCount = UpgradeMath.ScaleAmount(_maxCount, context.Card);
|
int maxCount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_maxCount, context), context.Card);
|
||||||
int targetCount = minCount == maxCount
|
int targetCount = minCount == maxCount
|
||||||
? minCount
|
? minCount
|
||||||
: RandomRoll.RollRange(minCount, maxCount, new RollContext(context.Source));
|
: RandomRoll.RollRange(minCount, maxCount, new RollContext(context.Source));
|
||||||
@@ -347,11 +355,11 @@ namespace KernelPanic.Combat
|
|||||||
"mint_update_manager" => Todo("TODO: update manager needs delayed repeat system."),
|
"mint_update_manager" => Todo("TODO: update manager needs delayed repeat system."),
|
||||||
"mint_cinnamon" => Todo("TODO: cinnamon needs shield effect support."),
|
"mint_cinnamon" => Todo("TODO: cinnamon needs shield effect support."),
|
||||||
"mint_nemo" => Todo("TODO: nemo needs conditional repeat based on shield."),
|
"mint_nemo" => Todo("TODO: nemo needs conditional repeat based on shield."),
|
||||||
"mint_timeshift" => Todo("TODO: timeshift snapshot needs delayed restore system."),
|
"mint_timeshift" => Todo("TODO: timeshift snapshot V3 needs delayed restore system before threshold/restore can be upgraded."),
|
||||||
"fedora_borrow_checker" => Todo("TODO: borrow checker needs shield and first-card tracking."),
|
"fedora_borrow_checker" => Todo("TODO: borrow checker needs shield and first-card tracking."),
|
||||||
"fedora_cargo_build" => Todo("TODO: cargo build --release needs overkill-to-shield handling."),
|
"fedora_cargo_build" => Todo("TODO: cargo build --release needs overkill-to-shield handling."),
|
||||||
"fedora_dnf_update" => Todo("TODO: dnf update needs Java played count and cost mutation."),
|
"fedora_dnf_update" => Todo("TODO: dnf update needs Java played count and cost mutation."),
|
||||||
"fedora_rawhide" => Todo("TODO: rawhide needs next-card cost mutation and crash-risk hook."),
|
"fedora_rawhide" => Todo("TODO: rawhide V3 needs permanent damage-growth doubling after growth effects exist."),
|
||||||
"fedora_selinux" => Todo("TODO: SELinux enforcing needs enemy attack mitigation statuses."),
|
"fedora_selinux" => Todo("TODO: SELinux enforcing needs enemy attack mitigation statuses."),
|
||||||
_ => Todo("TODO: card effect is not implemented yet.")
|
_ => Todo("TODO: card effect is not implemented yet.")
|
||||||
};
|
};
|
||||||
@@ -429,6 +437,7 @@ namespace KernelPanic.Combat
|
|||||||
_ => string.IsNullOrWhiteSpace(card.Definition.Description) ? "TODO: effect not implemented." : $"{card.Definition.Description}{marker}"
|
_ => string.IsNullOrWhiteSpace(card.Definition.Description) ? "TODO: effect not implemented." : $"{card.Definition.Description}{marker}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class UpgradeMath
|
public static class UpgradeMath
|
||||||
@@ -443,5 +452,9 @@ namespace KernelPanic.Combat
|
|||||||
return baseAmount + UnityEngine.Mathf.Max(0, card?.MagnitudeBonus ?? 0);
|
return baseAmount + UnityEngine.Mathf.Max(0, card?.MagnitudeBonus ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int ApplySourceFlatBonus(int amount, CombatContext context)
|
||||||
|
{
|
||||||
|
return amount + UnityEngine.Mathf.Max(0, context?.Source?.FlatEffectBonus ?? 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using KernelPanic.Core;
|
using KernelPanic.Core;
|
||||||
using KernelPanic.Data;
|
using KernelPanic.Data;
|
||||||
using KernelPanic.Run;
|
using KernelPanic.Run;
|
||||||
|
using KernelPanic.Meta;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -33,6 +34,9 @@ namespace KernelPanic.Combat
|
|||||||
private bool awaitingWaveContinue;
|
private bool awaitingWaveContinue;
|
||||||
private bool runLost;
|
private bool runLost;
|
||||||
private bool skipNextAllocateDraw;
|
private bool skipNextAllocateDraw;
|
||||||
|
private bool ubuntuEmptyHandRefillUsed;
|
||||||
|
private int fedoraCardsDiscountedThisTurn;
|
||||||
|
private int fedoraCrashChance = 10;
|
||||||
|
|
||||||
public TurnPhase CurrentPhase => currentPhase;
|
public TurnPhase CurrentPhase => currentPhase;
|
||||||
public RunConfig RunConfig => runConfig;
|
public RunConfig RunConfig => runConfig;
|
||||||
@@ -141,7 +145,13 @@ namespace KernelPanic.Combat
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool fedoraBonus = CanApplyFedoraBonus();
|
||||||
int cost = GetCardCost(card);
|
int cost = GetCardCost(card);
|
||||||
|
if (fedoraBonus)
|
||||||
|
{
|
||||||
|
cost = Mathf.Max(0, cost - 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (playerState.Cycles < cost)
|
if (playerState.Cycles < cost)
|
||||||
{
|
{
|
||||||
Log($"not enough cycles for {GetCardName(card)}");
|
Log($"not enough cycles for {GetCardName(card)}");
|
||||||
@@ -167,6 +177,29 @@ namespace KernelPanic.Combat
|
|||||||
LogPlayIntent(card);
|
LogPlayIntent(card);
|
||||||
card.SetTargetSnapshot(CaptureTargets(card));
|
card.SetTargetSnapshot(CaptureTargets(card));
|
||||||
|
|
||||||
|
if (fedoraBonus)
|
||||||
|
{
|
||||||
|
fedoraCardsDiscountedThisTurn++;
|
||||||
|
if (RandomRoll.RollRange(1, 100, new RollContext(playerState)) <= fedoraCrashChance)
|
||||||
|
{
|
||||||
|
fedoraCrashChance = 10;
|
||||||
|
if (IsDistro("fedora") && runConfig.DistroVersion >= 4)
|
||||||
|
{
|
||||||
|
playerState.Cycles += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
deckController.Discard(card);
|
||||||
|
pendingTargetCard = null;
|
||||||
|
selectedEnemyIndex = -1;
|
||||||
|
Log($"{GetCardName(card)} crashed under bleeding edge");
|
||||||
|
StateChanged?.Invoke();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fedoraCrashChance = Mathf.Min(90, fedoraCrashChance + 10);
|
||||||
|
playerState.DamageMultiplierPercent = runConfig.DistroVersion >= 2 ? 175 : 150;
|
||||||
|
}
|
||||||
|
|
||||||
switch (track)
|
switch (track)
|
||||||
{
|
{
|
||||||
case ResolutionTrack.InterpreterQueue:
|
case ResolutionTrack.InterpreterQueue:
|
||||||
@@ -184,7 +217,10 @@ namespace KernelPanic.Combat
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playerState.DamageMultiplierPercent = 100;
|
||||||
|
|
||||||
pendingTargetCard = null;
|
pendingTargetCard = null;
|
||||||
|
TryApplyUbuntuEmptyHandRefill();
|
||||||
StateChanged?.Invoke();
|
StateChanged?.Invoke();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -355,8 +391,12 @@ namespace KernelPanic.Combat
|
|||||||
runLost = false;
|
runLost = false;
|
||||||
awaitingWaveContinue = false;
|
awaitingWaveContinue = false;
|
||||||
loggedEffectTodos.Clear();
|
loggedEffectTodos.Clear();
|
||||||
|
ubuntuEmptyHandRefillUsed = false;
|
||||||
|
fedoraCrashChance = 10;
|
||||||
|
fedoraCardsDiscountedThisTurn = 0;
|
||||||
RandomRoll.Seed(runConfig.RunSeed);
|
RandomRoll.Seed(runConfig.RunSeed);
|
||||||
playerState = new CombatantState(runManager.EffectiveMaxUptime(), runManager.EffectiveRam(), runManager.EffectiveMaxCycles());
|
playerState = new CombatantState(runManager.EffectiveMaxUptime(), runManager.EffectiveRam(), runManager.EffectiveMaxCycles());
|
||||||
|
ApplyVersionState(playerState);
|
||||||
StartWave(preservePlayerUptime: false);
|
StartWave(preservePlayerUptime: false);
|
||||||
Log($"booted {runConfig.Distro.DisplayName} with {runManager.RunDeck.Count} cards");
|
Log($"booted {runConfig.Distro.DisplayName} with {runManager.RunDeck.Count} cards");
|
||||||
StateChanged?.Invoke();
|
StateChanged?.Invoke();
|
||||||
@@ -368,6 +408,7 @@ namespace KernelPanic.Combat
|
|||||||
if (!preservePlayerUptime || playerState == null)
|
if (!preservePlayerUptime || playerState == null)
|
||||||
{
|
{
|
||||||
playerState = new CombatantState(runManager.EffectiveMaxUptime(), runManager.EffectiveRam(), runManager.EffectiveMaxCycles());
|
playerState = new CombatantState(runManager.EffectiveMaxUptime(), runManager.EffectiveRam(), runManager.EffectiveMaxCycles());
|
||||||
|
ApplyVersionState(playerState);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -379,6 +420,7 @@ namespace KernelPanic.Combat
|
|||||||
playerState.Shield = 0;
|
playerState.Shield = 0;
|
||||||
playerState.IsDefeated = false;
|
playerState.IsDefeated = false;
|
||||||
playerState.MutableStatuses.Clear();
|
playerState.MutableStatuses.Clear();
|
||||||
|
ApplyVersionState(playerState);
|
||||||
}
|
}
|
||||||
|
|
||||||
handController = new HandController(playerState.Ram);
|
handController = new HandController(playerState.Ram);
|
||||||
@@ -413,12 +455,15 @@ namespace KernelPanic.Combat
|
|||||||
}
|
}
|
||||||
|
|
||||||
playerState.Cycles = playerState.MaxCycles;
|
playerState.Cycles = playerState.MaxCycles;
|
||||||
|
fedoraCardsDiscountedThisTurn = 0;
|
||||||
statusEffects.Tick(playerState, StatusTickTiming.StartOfTurn, playerState, damagePipeline);
|
statusEffects.Tick(playerState, StatusTickTiming.StartOfTurn, playerState, damagePipeline);
|
||||||
if (CheckLoss())
|
if (CheckLoss())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TryApplyUbuntuAptUpdate();
|
||||||
|
|
||||||
if (skipNextAllocateDraw)
|
if (skipNextAllocateDraw)
|
||||||
{
|
{
|
||||||
skipNextAllocateDraw = false;
|
skipNextAllocateDraw = false;
|
||||||
@@ -557,6 +602,71 @@ namespace KernelPanic.Combat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TryApplyUbuntuAptUpdate()
|
||||||
|
{
|
||||||
|
if (!IsDistro("ubuntu") || handController == null || playerState == null || handController.Cards.Count >= handController.RamCapacity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lookCount = runConfig.DistroVersion >= 2 ? 3 : 2;
|
||||||
|
if (!deckController.TryDrawCheapestFromTop(lookCount, out CardInstance card))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runConfig.DistroVersion >= 4)
|
||||||
|
{
|
||||||
|
card.TemporaryCostDelta -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handController.Add(card))
|
||||||
|
{
|
||||||
|
Log($"apt update: staged {GetCardName(card)} from top {lookCount}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryApplyUbuntuEmptyHandRefill()
|
||||||
|
{
|
||||||
|
if (!IsDistro("ubuntu") || runConfig.DistroVersion < 5 || ubuntuEmptyHandRefillUsed || handController == null || handController.Cards.Count > 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ubuntuEmptyHandRefillUsed = true;
|
||||||
|
DrawCardsToHand(playerState.Ram, "ubuntu 24.04 refill");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanApplyFedoraBonus()
|
||||||
|
{
|
||||||
|
if (!IsDistro("fedora") || playerState == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int limit = runConfig.DistroVersion >= 5 ? 2 : 1;
|
||||||
|
return fedoraCardsDiscountedThisTurn < limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyVersionState(CombatantState state)
|
||||||
|
{
|
||||||
|
if (state == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.forceMaxRolls = IsDistro("mint");
|
||||||
|
state.IgnoreDamageMultipliers = IsDistro("mint");
|
||||||
|
state.AllowFlatDamageBuffs = IsDistro("mint") && runConfig.DistroVersion >= 4;
|
||||||
|
state.FlatEffectBonus = IsDistro("mint") && runConfig.DistroVersion >= 2 ? 2 : 0;
|
||||||
|
state.DamageMultiplierPercent = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDistro(string id)
|
||||||
|
{
|
||||||
|
return string.Equals(runConfig?.Distro?.Id, id, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
private CombatContext BuildContext(CardInstance card, IReadOnlyList<CombatantState> targets)
|
private CombatContext BuildContext(CardInstance card, IReadOnlyList<CombatantState> targets)
|
||||||
{
|
{
|
||||||
return new CombatContext(card, playerState, targets, this, damagePipeline, statusEffects, deckController, handController, enemies);
|
return new CombatContext(card, playerState, targets, this, damagePipeline, statusEffects, deckController, handController, enemies);
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ namespace KernelPanic.Combat
|
|||||||
public int MaxCycles { get; set; }
|
public int MaxCycles { get; set; }
|
||||||
public bool IsDefeated { get; set; }
|
public bool IsDefeated { get; set; }
|
||||||
public bool forceMaxRolls { get; set; }
|
public bool forceMaxRolls { get; set; }
|
||||||
|
public bool IgnoreDamageMultipliers { get; set; }
|
||||||
|
public bool AllowFlatDamageBuffs { get; set; }
|
||||||
|
public int FlatEffectBonus { get; set; }
|
||||||
|
public int DamageMultiplierPercent { get; set; } = 100;
|
||||||
public IReadOnlyList<StatusInstance> Statuses => _statuses;
|
public IReadOnlyList<StatusInstance> Statuses => _statuses;
|
||||||
public IReadOnlyDictionary<StatusType, int> ActiveStatuses => BuildStatusSnapshot();
|
public IReadOnlyDictionary<StatusType, int> ActiveStatuses => BuildStatusSnapshot();
|
||||||
|
|
||||||
|
|||||||
@@ -74,18 +74,32 @@ namespace KernelPanic.Combat
|
|||||||
|
|
||||||
private static int ApplyMultipliers(int amount, DamageRequest request)
|
private static int ApplyMultipliers(int amount, DamageRequest request)
|
||||||
{
|
{
|
||||||
// TODO: Apply buffs/debuffs and Mint's no-multiplier rule here.
|
if (request.Source != null && request.Source.IgnoreDamageMultipliers)
|
||||||
|
{
|
||||||
|
// Mint V1+ ignores multiplicative damage modifiers. Mint V4 still allows flat
|
||||||
|
// bonuses before this step; no flat buff status exists yet, so the flag is a hook.
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int multiplier = request.Source == null ? 100 : Mathf.Max(0, request.Source.DamageMultiplierPercent);
|
||||||
|
return Mathf.RoundToInt(amount * (multiplier / 100f));
|
||||||
|
}
|
||||||
|
|
||||||
private static int ApplyCrit(int amount, DamageRequest request)
|
private static int ApplyCrit(int amount, DamageRequest request)
|
||||||
{
|
{
|
||||||
|
if (request.Source != null && request.Source.forceMaxRolls)
|
||||||
|
{
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add crit calculation and crit modifiers.
|
// TODO: Add crit calculation and crit modifiers.
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ApplyResistancesAndWeaknesses(int amount, DamageRequest request)
|
private static int ApplyResistancesAndWeaknesses(int amount, DamageRequest request)
|
||||||
{
|
{
|
||||||
|
// TODO: Enemy resistances/reductions are not authored yet. Mint V5 should re-resolve
|
||||||
|
// a once-per-combat reduced effect here once those reduction sources exist.
|
||||||
// TODO: Apply enemy language resistances and weaknesses here.
|
// TODO: Apply enemy language resistances and weaknesses here.
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,43 @@ namespace KernelPanic.Combat
|
|||||||
return drawn;
|
return drawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryDrawCheapestFromTop(int lookCount, out CardInstance card)
|
||||||
|
{
|
||||||
|
card = null;
|
||||||
|
if (lookCount <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_drawPile.Count == 0)
|
||||||
|
{
|
||||||
|
ReshuffleDiscardIntoDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_drawPile.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int inspectCount = System.Math.Min(lookCount, _drawPile.Count);
|
||||||
|
int selectedIndex = _drawPile.Count - 1;
|
||||||
|
int selectedCost = CombatManager.GetCardCost(_drawPile[selectedIndex]);
|
||||||
|
for (int offset = 1; offset < inspectCount; offset++)
|
||||||
|
{
|
||||||
|
int index = _drawPile.Count - 1 - offset;
|
||||||
|
int cost = CombatManager.GetCardCost(_drawPile[index]);
|
||||||
|
if (cost < selectedCost)
|
||||||
|
{
|
||||||
|
selectedCost = cost;
|
||||||
|
selectedIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
card = _drawPile[selectedIndex];
|
||||||
|
_drawPile.RemoveAt(selectedIndex);
|
||||||
|
return card != null;
|
||||||
|
}
|
||||||
|
|
||||||
public void Discard(CardInstance card)
|
public void Discard(CardInstance card)
|
||||||
{
|
{
|
||||||
if (card != null)
|
if (card != null)
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace KernelPanic.Meta
|
||||||
|
{
|
||||||
|
public static class DistroVersionCatalog
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, string[]> ReleaseLabels = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["ubuntu"] = new[] { "20.04", "21.04", "22.04", "23.04", "24.04" },
|
||||||
|
["fedora"] = new[] { "38", "39", "40", "41", "42" },
|
||||||
|
["mint"] = new[] { "21", "21.2", "21.3", "22", "22.1" }
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string GetReleaseLabel(string unitId, int version)
|
||||||
|
{
|
||||||
|
int safeVersion = Math.Max(1, Math.Min(GachaTuning.MaxVersion, version));
|
||||||
|
if (!string.IsNullOrWhiteSpace(unitId) && ReleaseLabels.TryGetValue(unitId, out string[] labels) && safeVersion <= labels.Length)
|
||||||
|
{
|
||||||
|
return labels[safeVersion - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"V{safeVersion}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetEffectSummary(string unitId, int version)
|
||||||
|
{
|
||||||
|
int safeVersion = Math.Max(1, Math.Min(GachaTuning.MaxVersion, version));
|
||||||
|
return (unitId ?? string.Empty).ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"ubuntu" => safeVersion switch
|
||||||
|
{
|
||||||
|
1 => "start of turn: apt update chooses the cheaper of top 2 draw cards",
|
||||||
|
2 => "apt update looks at top 3 instead of top 2",
|
||||||
|
3 => "ask ubuntu draws 3 instead of 2",
|
||||||
|
4 => "apt update card costs 1 less this turn",
|
||||||
|
5 => "once per combat, empty hand refills up to RAM",
|
||||||
|
_ => "--"
|
||||||
|
},
|
||||||
|
"fedora" => safeVersion switch
|
||||||
|
{
|
||||||
|
1 => "first card each turn costs 1 less and deals +50%, with crash risk",
|
||||||
|
2 => "first-card damage bonus rises to +75%",
|
||||||
|
3 => "rawhide TODO: double permanent damage growth on non-crash first-card turn",
|
||||||
|
4 => "crashes grant +1 Cycle that turn",
|
||||||
|
5 => "first two cards each turn get the discount, bonus, and independent crash rolls",
|
||||||
|
_ => "--"
|
||||||
|
},
|
||||||
|
"mint" => safeVersion switch
|
||||||
|
{
|
||||||
|
1 => "random values roll max; no crits; ignores multiplicative damage buffs",
|
||||||
|
2 => "fixed effect values are treated as +2",
|
||||||
|
3 => "timeshift snapshot TODO: stronger restore when delayed restore exists",
|
||||||
|
4 => "flat +damage can apply; multiplicative crit/mult remains ignored",
|
||||||
|
5 => "TODO: re-resolve reduced effect when enemy reduction/coercion exists",
|
||||||
|
_ => "--"
|
||||||
|
},
|
||||||
|
_ => safeVersion == 1 ? "base release" : $"TODO: V{safeVersion} release effect"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace KernelPanic.Meta
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tunable gacha progression amounts. Kept in code until designers need asset-driven economy tuning.
|
||||||
|
/// </summary>
|
||||||
|
public static class GachaTuning
|
||||||
|
{
|
||||||
|
public const int MaxVersion = 5;
|
||||||
|
public const int MergesPerDupe = 20;
|
||||||
|
public const int MergesOnCharacterBonus = 10;
|
||||||
|
public const float MergesMaxVersionOverflowMultiplier = 1.5f;
|
||||||
|
public static int DupeConsolationBandwidth => 0;
|
||||||
|
public static readonly int[] VersionCosts = { 50, 120, 250, 500 };
|
||||||
|
|
||||||
|
public static int GetVersionUpgradeCost(int targetVersion)
|
||||||
|
{
|
||||||
|
if (targetVersion <= 1 || targetVersion > MaxVersion)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = targetVersion - 2;
|
||||||
|
return index >= 0 && index < VersionCosts.Length ? VersionCosts[index] : VersionCosts[VersionCosts.Length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ namespace KernelPanic.Meta
|
|||||||
public sealed class PlayerCollection
|
public sealed class PlayerCollection
|
||||||
{
|
{
|
||||||
private readonly List<DistroDefinition> _ownedUnits = new();
|
private readonly List<DistroDefinition> _ownedUnits = new();
|
||||||
|
private readonly Dictionary<string, int> _versions = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private int _featuredIndex;
|
private int _featuredIndex;
|
||||||
|
|
||||||
// TODO: Populate owned units from SaveService during bootstrap.
|
// TODO: Populate owned units from SaveService during bootstrap.
|
||||||
@@ -19,17 +20,47 @@ namespace KernelPanic.Meta
|
|||||||
public event Action Changed;
|
public event Action Changed;
|
||||||
|
|
||||||
public void Add(DistroDefinition unit)
|
public void Add(DistroDefinition unit)
|
||||||
|
{
|
||||||
|
Add(unit, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(DistroDefinition unit, int version)
|
||||||
|
{
|
||||||
|
Add(unit, version, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSilently(DistroDefinition unit, int version)
|
||||||
|
{
|
||||||
|
Add(unit, version, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Add(DistroDefinition unit, int version, bool notify)
|
||||||
{
|
{
|
||||||
if (unit == null)
|
if (unit == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsOwned(unit.Id))
|
||||||
|
{
|
||||||
|
SetVersion(unit.Id, version, notify);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Persistence is wired by the owner so this domain class stays IO-free.
|
// Persistence is wired by the owner so this domain class stays IO-free.
|
||||||
_ownedUnits.Add(unit);
|
_ownedUnits.Add(unit);
|
||||||
|
_versions[unit.Id] = Math.Max(1, version);
|
||||||
_featuredIndex = Math.Clamp(_featuredIndex, 0, _ownedUnits.Count - 1);
|
_featuredIndex = Math.Clamp(_featuredIndex, 0, _ownedUnits.Count - 1);
|
||||||
|
if (notify)
|
||||||
|
{
|
||||||
Changed?.Invoke();
|
Changed?.Invoke();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsOwned(string unitId)
|
||||||
|
{
|
||||||
|
return GetOwnedCount(unitId) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
public int GetOwnedCount(string unitId)
|
public int GetOwnedCount(string unitId)
|
||||||
{
|
{
|
||||||
@@ -51,6 +82,46 @@ namespace KernelPanic.Meta
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetVersion(string unitId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(unitId))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _versions.TryGetValue(unitId, out int version) ? Math.Max(1, version) : IsOwned(unitId) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVersion(string unitId, int version)
|
||||||
|
{
|
||||||
|
SetVersion(unitId, version, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVersionSilently(string unitId, int version)
|
||||||
|
{
|
||||||
|
SetVersion(unitId, version, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetVersion(string unitId, int version, bool notify)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(unitId) || !IsOwned(unitId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int safeVersion = Math.Max(1, version);
|
||||||
|
if (_versions.TryGetValue(unitId, out int oldVersion) && oldVersion == safeVersion)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_versions[unitId] = safeVersion;
|
||||||
|
if (notify)
|
||||||
|
{
|
||||||
|
Changed?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SelectNextFeatured()
|
public void SelectNextFeatured()
|
||||||
{
|
{
|
||||||
if (_ownedUnits.Count < 2)
|
if (_ownedUnits.Count < 2)
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using KernelPanic.Core;
|
||||||
|
using KernelPanic.Data;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KernelPanic.Meta
|
||||||
|
{
|
||||||
|
public static class PullResolver
|
||||||
|
{
|
||||||
|
public static event Action<PullResolutionResult> PullResolved;
|
||||||
|
|
||||||
|
public static PullResolutionResult Resolve(IEnumerable<DistroDefinition> pullResults, PullResolutionContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.SaveData ??= SaveData.CreateDefault();
|
||||||
|
context.SaveData.EnsureLists();
|
||||||
|
|
||||||
|
List<PullResolutionOutcome> outcomes = new();
|
||||||
|
if (pullResults != null)
|
||||||
|
{
|
||||||
|
foreach (DistroDefinition unit in pullResults)
|
||||||
|
{
|
||||||
|
outcomes.Add(ResolveOne(unit, context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PullResolutionResult result = new(outcomes);
|
||||||
|
PullResolved?.Invoke(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PullResolutionResult Resolve(IEnumerable<string> pullResultIds, PullResolutionContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DistroDefinition> units = new();
|
||||||
|
if (pullResultIds != null)
|
||||||
|
{
|
||||||
|
foreach (string id in pullResultIds)
|
||||||
|
{
|
||||||
|
units.Add(context.DistroDatabase == null ? null : context.DistroDatabase.FindById(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resolve(units, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PullResolutionResult ResolveAndSave(IEnumerable<DistroDefinition> pullResults, PullResolutionContext context, SaveService saveService)
|
||||||
|
{
|
||||||
|
PullResolutionResult result = Resolve(pullResults, context);
|
||||||
|
saveService?.Save(context.SaveData);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PullResolutionResult ResolveAndSave(IEnumerable<string> pullResultIds, PullResolutionContext context, SaveService saveService)
|
||||||
|
{
|
||||||
|
PullResolutionResult result = Resolve(pullResultIds, context);
|
||||||
|
saveService?.Save(context.SaveData);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PullResolutionOutcome ResolveOne(DistroDefinition unit, PullResolutionContext context)
|
||||||
|
{
|
||||||
|
if (unit == null || string.IsNullOrWhiteSpace(unit.Id))
|
||||||
|
{
|
||||||
|
return new PullResolutionOutcome(null, PullOutcomeKind.Invalid, 0, Array.Empty<Language>());
|
||||||
|
}
|
||||||
|
|
||||||
|
OwnedUnitSaveEntry owned = context.SaveData.FindOwnedUnit(unit.Id);
|
||||||
|
bool ownedInCollection = context.Collection != null && context.Collection.IsOwned(unit.Id);
|
||||||
|
if (owned == null && !ownedInCollection)
|
||||||
|
{
|
||||||
|
IReadOnlyList<Language> before = SnapshotUnlockedLanguages(context.Collection);
|
||||||
|
context.SaveData.AddOwnedUnit(unit.Id, 1);
|
||||||
|
context.Collection?.AddSilently(unit, 1);
|
||||||
|
IReadOnlyList<Language> unlocked = NewlyUnlockedLanguages(before, context.Collection, unit);
|
||||||
|
return new PullResolutionOutcome(unit.Id, PullOutcomeKind.Granted, 0, unlocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
owned ??= context.SaveData.AddOwnedUnit(unit.Id, context.Collection?.GetVersion(unit.Id) ?? 1);
|
||||||
|
int version = Mathf.Clamp(owned.version, 1, GachaTuning.MaxVersion);
|
||||||
|
owned.version = version;
|
||||||
|
bool overflow = version >= GachaTuning.MaxVersion;
|
||||||
|
int merges = CalculateDupeMerges(overflow, unit.Id, context.FocusUnitId);
|
||||||
|
context.SaveData.merges = Math.Max(0, context.SaveData.merges) + merges;
|
||||||
|
if (GachaTuning.DupeConsolationBandwidth > 0)
|
||||||
|
{
|
||||||
|
context.SaveData.standardPullCurrency += GachaTuning.DupeConsolationBandwidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Collection?.AddSilently(unit, version);
|
||||||
|
return new PullResolutionOutcome(unit.Id, overflow ? PullOutcomeKind.DupeOverflow : PullOutcomeKind.Dupe, merges, Array.Empty<Language>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CalculateDupeMerges(bool overflow, string unitId, string focusUnitId)
|
||||||
|
{
|
||||||
|
float baseAmount = GachaTuning.MergesPerDupe;
|
||||||
|
if (overflow)
|
||||||
|
{
|
||||||
|
baseAmount *= GachaTuning.MergesMaxVersionOverflowMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
int merges = Mathf.RoundToInt(baseAmount);
|
||||||
|
if (!string.IsNullOrWhiteSpace(focusUnitId) && string.Equals(unitId, focusUnitId, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
merges += GachaTuning.MergesOnCharacterBonus;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.Max(1, merges);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<Language> SnapshotUnlockedLanguages(PlayerCollection collection)
|
||||||
|
{
|
||||||
|
List<Language> unlocked = new();
|
||||||
|
if (collection == null)
|
||||||
|
{
|
||||||
|
return unlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
IReadOnlyList<LanguageCatalogEntry> languages = LanguageCatalog.All;
|
||||||
|
for (int i = 0; i < languages.Count; i++)
|
||||||
|
{
|
||||||
|
if (LanguageUnlock.IsUnlocked(languages[i].Language, collection))
|
||||||
|
{
|
||||||
|
unlocked.Add(languages[i].Language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<Language> NewlyUnlockedLanguages(IReadOnlyList<Language> before, PlayerCollection collection, DistroDefinition unit)
|
||||||
|
{
|
||||||
|
List<Language> unlocked = new();
|
||||||
|
if (collection == null || unit == null)
|
||||||
|
{
|
||||||
|
return unlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddNewlyUnlockedLanguage(unit.PrimaryLanguage, before, collection, unlocked);
|
||||||
|
AddNewlyUnlockedLanguage(unit.SecondaryLanguage, before, collection, unlocked);
|
||||||
|
|
||||||
|
return unlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddNewlyUnlockedLanguage(Language language, IReadOnlyList<Language> before, PlayerCollection collection, List<Language> unlocked)
|
||||||
|
{
|
||||||
|
if (!ContainsLanguage(unlocked, language) && !ContainsLanguage(before, language) && LanguageUnlock.IsUnlocked(language, collection))
|
||||||
|
{
|
||||||
|
unlocked.Add(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ContainsLanguage(IReadOnlyList<Language> languages, Language language)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < languages.Count; i++)
|
||||||
|
{
|
||||||
|
if (languages[i] == language)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PullResolutionContext
|
||||||
|
{
|
||||||
|
public PullResolutionContext(SaveData saveData, PlayerCollection collection, DistroDatabase distroDatabase = null, string focusUnitId = null)
|
||||||
|
{
|
||||||
|
SaveData = saveData;
|
||||||
|
Collection = collection;
|
||||||
|
DistroDatabase = distroDatabase;
|
||||||
|
FocusUnitId = focusUnitId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SaveData SaveData { get; set; }
|
||||||
|
public PlayerCollection Collection { get; }
|
||||||
|
public DistroDatabase DistroDatabase { get; }
|
||||||
|
public string FocusUnitId { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PullResolutionResult
|
||||||
|
{
|
||||||
|
public PullResolutionResult(IReadOnlyList<PullResolutionOutcome> outcomes)
|
||||||
|
{
|
||||||
|
Outcomes = outcomes ?? Array.Empty<PullResolutionOutcome>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<PullResolutionOutcome> Outcomes { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct PullResolutionOutcome
|
||||||
|
{
|
||||||
|
public PullResolutionOutcome(string unitId, PullOutcomeKind kind, int mergesAwarded, IReadOnlyList<Language> languagesNewlyUnlocked)
|
||||||
|
{
|
||||||
|
UnitId = unitId;
|
||||||
|
Kind = kind;
|
||||||
|
MergesAwarded = mergesAwarded;
|
||||||
|
LanguagesNewlyUnlocked = languagesNewlyUnlocked ?? Array.Empty<Language>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UnitId { get; }
|
||||||
|
public PullOutcomeKind Kind { get; }
|
||||||
|
public int MergesAwarded { get; }
|
||||||
|
public IReadOnlyList<Language> LanguagesNewlyUnlocked { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PullOutcomeKind
|
||||||
|
{
|
||||||
|
Invalid,
|
||||||
|
Granted,
|
||||||
|
Dupe,
|
||||||
|
DupeOverflow
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,6 +62,8 @@ namespace KernelPanic.Meta
|
|||||||
public int rootCredits = GachaService.TestRootCreditsBalance;
|
public int rootCredits = GachaService.TestRootCreditsBalance;
|
||||||
public int standardPullCurrency;
|
public int standardPullCurrency;
|
||||||
public int limitedPullCurrency;
|
public int limitedPullCurrency;
|
||||||
|
public int merges;
|
||||||
|
public List<OwnedUnitSaveEntry> ownedUnits = new();
|
||||||
public List<string> ownedUnitIds = new();
|
public List<string> ownedUnitIds = new();
|
||||||
public List<string> bannerPoolIds = new();
|
public List<string> bannerPoolIds = new();
|
||||||
public GachaBannerState beginnerBannerState = new(GachaService.BeginnerBannerId);
|
public GachaBannerState beginnerBannerState = new(GachaService.BeginnerBannerId);
|
||||||
@@ -74,6 +76,7 @@ namespace KernelPanic.Meta
|
|||||||
|
|
||||||
public void EnsureLists()
|
public void EnsureLists()
|
||||||
{
|
{
|
||||||
|
ownedUnits ??= new List<OwnedUnitSaveEntry>();
|
||||||
ownedUnitIds ??= new List<string>();
|
ownedUnitIds ??= new List<string>();
|
||||||
bannerPoolIds ??= new List<string>();
|
bannerPoolIds ??= new List<string>();
|
||||||
beginnerBannerState ??= new GachaBannerState(GachaService.BeginnerBannerId);
|
beginnerBannerState ??= new GachaBannerState(GachaService.BeginnerBannerId);
|
||||||
@@ -81,7 +84,114 @@ namespace KernelPanic.Meta
|
|||||||
beginnerBannerState.EnsureLists();
|
beginnerBannerState.EnsureLists();
|
||||||
lastRunLoadout ??= new LastRunLoadoutSaveEntry();
|
lastRunLoadout ??= new LastRunLoadoutSaveEntry();
|
||||||
lastRunLoadout.EnsureLists();
|
lastRunLoadout.EnsureLists();
|
||||||
|
merges = Math.Max(0, merges);
|
||||||
|
MigrateLegacyOwnedUnitIds();
|
||||||
|
NormalizeOwnedUnits();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OwnedUnitSaveEntry FindOwnedUnit(string id)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureLists();
|
||||||
|
for (int i = 0; i < ownedUnits.Count; i++)
|
||||||
|
{
|
||||||
|
OwnedUnitSaveEntry entry = ownedUnits[i];
|
||||||
|
if (entry != null && string.Equals(entry.id, id, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsUnitOwned(string id)
|
||||||
|
{
|
||||||
|
return FindOwnedUnit(id) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OwnedUnitSaveEntry AddOwnedUnit(string id, int version)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureLists();
|
||||||
|
OwnedUnitSaveEntry existing = FindOwnedUnit(id);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.version = Math.Max(1, Math.Min(GachaTuning.MaxVersion, existing.version));
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
OwnedUnitSaveEntry entry = new()
|
||||||
|
{
|
||||||
|
id = id,
|
||||||
|
version = Math.Max(1, Math.Min(GachaTuning.MaxVersion, version))
|
||||||
|
};
|
||||||
|
ownedUnits.Add(entry);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MigrateLegacyOwnedUnitIds()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ownedUnitIds.Count; i++)
|
||||||
|
{
|
||||||
|
string id = ownedUnitIds[i];
|
||||||
|
if (string.IsNullOrWhiteSpace(id) || HasOwnedUnitEntry(id))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ownedUnits.Add(new OwnedUnitSaveEntry
|
||||||
|
{
|
||||||
|
id = id,
|
||||||
|
version = 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NormalizeOwnedUnits()
|
||||||
|
{
|
||||||
|
HashSet<string> seen = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
for (int i = ownedUnits.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
OwnedUnitSaveEntry entry = ownedUnits[i];
|
||||||
|
if (entry == null || string.IsNullOrWhiteSpace(entry.id) || !seen.Add(entry.id))
|
||||||
|
{
|
||||||
|
ownedUnits.RemoveAt(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.version = Math.Max(1, Math.Min(GachaTuning.MaxVersion, entry.version));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasOwnedUnitEntry(string id)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ownedUnits.Count; i++)
|
||||||
|
{
|
||||||
|
OwnedUnitSaveEntry entry = ownedUnits[i];
|
||||||
|
if (entry != null && string.Equals(entry.id, id, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public sealed class OwnedUnitSaveEntry
|
||||||
|
{
|
||||||
|
public string id;
|
||||||
|
public int version = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KernelPanic.Meta
|
||||||
|
{
|
||||||
|
public static class VersionUpgrader
|
||||||
|
{
|
||||||
|
public static event Action<VersionUpgradeResult> UnitUpgraded;
|
||||||
|
|
||||||
|
public static bool TryUpgrade(string unitId, SaveData state, PlayerCollection collection, out VersionUpgradeResult result)
|
||||||
|
{
|
||||||
|
result = TryUpgrade(unitId, state, collection);
|
||||||
|
return result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VersionUpgradeResult TryUpgradeAndSave(string unitId, SaveData state, PlayerCollection collection, SaveService saveService)
|
||||||
|
{
|
||||||
|
VersionUpgradeResult result = TryUpgrade(unitId, state, collection);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
saveService?.Save(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VersionUpgradeResult TryUpgrade(string unitId, SaveData state, PlayerCollection collection)
|
||||||
|
{
|
||||||
|
state ??= SaveData.CreateDefault();
|
||||||
|
state.EnsureLists();
|
||||||
|
|
||||||
|
OwnedUnitSaveEntry entry = state.FindOwnedUnit(unitId);
|
||||||
|
if (entry == null && collection != null && collection.IsOwned(unitId))
|
||||||
|
{
|
||||||
|
entry = state.AddOwnedUnit(unitId, collection.GetVersion(unitId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
return VersionUpgradeResult.Failed(unitId, VersionUpgradeFailureReason.NotOwned);
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentVersion = Mathf.Clamp(entry.version, 1, GachaTuning.MaxVersion);
|
||||||
|
if (currentVersion >= GachaTuning.MaxVersion)
|
||||||
|
{
|
||||||
|
return VersionUpgradeResult.Failed(unitId, VersionUpgradeFailureReason.MaxVersion, currentVersion, currentVersion, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int targetVersion = currentVersion + 1;
|
||||||
|
int cost = GachaTuning.GetVersionUpgradeCost(targetVersion);
|
||||||
|
if (state.merges < cost)
|
||||||
|
{
|
||||||
|
return VersionUpgradeResult.Failed(unitId, VersionUpgradeFailureReason.InsufficientMerges, currentVersion, targetVersion, cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.merges -= cost;
|
||||||
|
entry.version = targetVersion;
|
||||||
|
collection?.SetVersionSilently(unitId, targetVersion);
|
||||||
|
|
||||||
|
VersionUpgradeResult result = VersionUpgradeResult.Upgraded(unitId, currentVersion, targetVersion, cost, state.merges);
|
||||||
|
UnitUpgraded?.Invoke(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct VersionUpgradeResult
|
||||||
|
{
|
||||||
|
private VersionUpgradeResult(string unitId, bool success, VersionUpgradeFailureReason failureReason, int fromVersion, int toVersion, int cost, int mergesRemaining)
|
||||||
|
{
|
||||||
|
UnitId = unitId;
|
||||||
|
Success = success;
|
||||||
|
FailureReason = failureReason;
|
||||||
|
FromVersion = fromVersion;
|
||||||
|
ToVersion = toVersion;
|
||||||
|
Cost = cost;
|
||||||
|
MergesRemaining = mergesRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UnitId { get; }
|
||||||
|
public bool Success { get; }
|
||||||
|
public VersionUpgradeFailureReason FailureReason { get; }
|
||||||
|
public int FromVersion { get; }
|
||||||
|
public int ToVersion { get; }
|
||||||
|
public int Cost { get; }
|
||||||
|
public int MergesRemaining { get; }
|
||||||
|
|
||||||
|
public static VersionUpgradeResult Upgraded(string unitId, int fromVersion, int toVersion, int cost, int mergesRemaining)
|
||||||
|
{
|
||||||
|
return new VersionUpgradeResult(unitId, true, VersionUpgradeFailureReason.None, fromVersion, toVersion, cost, mergesRemaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VersionUpgradeResult Failed(string unitId, VersionUpgradeFailureReason reason, int fromVersion = 0, int toVersion = 0, int cost = 0)
|
||||||
|
{
|
||||||
|
return new VersionUpgradeResult(unitId, false, reason, fromVersion, toVersion, cost, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum VersionUpgradeFailureReason
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
NotOwned,
|
||||||
|
MaxVersion,
|
||||||
|
InsufficientMerges
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,17 +10,23 @@ namespace KernelPanic.Run
|
|||||||
public sealed class RunConfig
|
public sealed class RunConfig
|
||||||
{
|
{
|
||||||
public RunConfig(DistroDefinition distro, Language primaryLanguage, Language secondaryLanguage, IReadOnlyList<CardDefinition> startingDeck)
|
public RunConfig(DistroDefinition distro, Language primaryLanguage, Language secondaryLanguage, IReadOnlyList<CardDefinition> startingDeck)
|
||||||
: this(distro, primaryLanguage, secondaryLanguage, startingDeck, 0)
|
: this(distro, primaryLanguage, secondaryLanguage, startingDeck, 0, 1)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public RunConfig(DistroDefinition distro, Language primaryLanguage, Language secondaryLanguage, IReadOnlyList<CardDefinition> startingDeck, int runSeed)
|
public RunConfig(DistroDefinition distro, Language primaryLanguage, Language secondaryLanguage, IReadOnlyList<CardDefinition> startingDeck, int runSeed)
|
||||||
|
: this(distro, primaryLanguage, secondaryLanguage, startingDeck, runSeed, 1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RunConfig(DistroDefinition distro, Language primaryLanguage, Language secondaryLanguage, IReadOnlyList<CardDefinition> startingDeck, int runSeed, int distroVersion)
|
||||||
{
|
{
|
||||||
Distro = distro;
|
Distro = distro;
|
||||||
PrimaryLanguage = primaryLanguage;
|
PrimaryLanguage = primaryLanguage;
|
||||||
SecondaryLanguage = secondaryLanguage;
|
SecondaryLanguage = secondaryLanguage;
|
||||||
StartingDeck = startingDeck;
|
StartingDeck = startingDeck;
|
||||||
RunSeed = runSeed;
|
RunSeed = runSeed;
|
||||||
|
DistroVersion = UnityEngine.Mathf.Clamp(distroVersion, 1, KernelPanic.Meta.GachaTuning.MaxVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DistroDefinition Distro { get; }
|
public DistroDefinition Distro { get; }
|
||||||
@@ -28,5 +34,6 @@ namespace KernelPanic.Run
|
|||||||
public Language SecondaryLanguage { get; }
|
public Language SecondaryLanguage { get; }
|
||||||
public IReadOnlyList<CardDefinition> StartingDeck { get; }
|
public IReadOnlyList<CardDefinition> StartingDeck { get; }
|
||||||
public int RunSeed { get; }
|
public int RunSeed { get; }
|
||||||
|
public int DistroVersion { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ namespace KernelPanic.Run
|
|||||||
|
|
||||||
public static void Set(DistroDefinition distro, IReadOnlyList<CardDefinition> equippedCards, Language primaryLanguage, Language secondaryLanguage)
|
public static void Set(DistroDefinition distro, IReadOnlyList<CardDefinition> equippedCards, Language primaryLanguage, Language secondaryLanguage)
|
||||||
{
|
{
|
||||||
pendingRun = new PendingRun(distro, equippedCards, primaryLanguage, secondaryLanguage, Environment.TickCount);
|
Set(distro, equippedCards, primaryLanguage, secondaryLanguage, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Set(DistroDefinition distro, IReadOnlyList<CardDefinition> equippedCards, Language primaryLanguage, Language secondaryLanguage, int distroVersion)
|
||||||
|
{
|
||||||
|
pendingRun = new PendingRun(distro, equippedCards, primaryLanguage, secondaryLanguage, Environment.TickCount, distroVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryCreateRunConfig(LanguageDeckDatabase languageDeckDatabase, out RunConfig config)
|
public static bool TryCreateRunConfig(LanguageDeckDatabase languageDeckDatabase, out RunConfig config)
|
||||||
@@ -41,13 +46,15 @@ namespace KernelPanic.Run
|
|||||||
private readonly Language primaryLanguage;
|
private readonly Language primaryLanguage;
|
||||||
private readonly Language secondaryLanguage;
|
private readonly Language secondaryLanguage;
|
||||||
private readonly int runSeed;
|
private readonly int runSeed;
|
||||||
|
private readonly int distroVersion;
|
||||||
|
|
||||||
public PendingRun(DistroDefinition distro, IReadOnlyList<CardDefinition> cards, Language primaryLanguage, Language secondaryLanguage, int runSeed)
|
public PendingRun(DistroDefinition distro, IReadOnlyList<CardDefinition> cards, Language primaryLanguage, Language secondaryLanguage, int runSeed, int distroVersion)
|
||||||
{
|
{
|
||||||
this.distro = distro;
|
this.distro = distro;
|
||||||
this.primaryLanguage = primaryLanguage;
|
this.primaryLanguage = primaryLanguage;
|
||||||
this.secondaryLanguage = secondaryLanguage;
|
this.secondaryLanguage = secondaryLanguage;
|
||||||
this.runSeed = runSeed;
|
this.runSeed = runSeed;
|
||||||
|
this.distroVersion = distroVersion;
|
||||||
|
|
||||||
if (cards == null)
|
if (cards == null)
|
||||||
{
|
{
|
||||||
@@ -70,7 +77,7 @@ namespace KernelPanic.Run
|
|||||||
List<CardDefinition> startingDeck = new(equippedCards);
|
List<CardDefinition> startingDeck = new(equippedCards);
|
||||||
AddLanguageDeck(languageDeckDatabase, primaryLanguage, startingDeck);
|
AddLanguageDeck(languageDeckDatabase, primaryLanguage, startingDeck);
|
||||||
AddLanguageDeck(languageDeckDatabase, secondaryLanguage, startingDeck);
|
AddLanguageDeck(languageDeckDatabase, secondaryLanguage, startingDeck);
|
||||||
return new RunConfig(distro, primaryLanguage, secondaryLanguage, startingDeck, runSeed);
|
return new RunConfig(distro, primaryLanguage, secondaryLanguage, startingDeck, runSeed, distroVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddLanguageDeck(LanguageDeckDatabase languageDeckDatabase, Language language, List<CardDefinition> target)
|
private static void AddLanguageDeck(LanguageDeckDatabase languageDeckDatabase, Language language, List<CardDefinition> target)
|
||||||
|
|||||||
@@ -18,11 +18,14 @@ namespace KernelPanic.UI
|
|||||||
private readonly List<VisualElement> _rows = new();
|
private readonly List<VisualElement> _rows = new();
|
||||||
|
|
||||||
private VisualElement _list;
|
private VisualElement _list;
|
||||||
|
private ScrollView _detailScroll;
|
||||||
private VisualElement _detail;
|
private VisualElement _detail;
|
||||||
private FontAsset _monospaceFont;
|
private FontAsset _monospaceFont;
|
||||||
private LanguageDeckDatabase _languageDeckDatabase;
|
private LanguageDeckDatabase _languageDeckDatabase;
|
||||||
private CardDatabase _cardDatabase;
|
private CardDatabase _cardDatabase;
|
||||||
private PlayerCollection _playerCollection;
|
private PlayerCollection _playerCollection;
|
||||||
|
private Func<int> _getMergesBalance;
|
||||||
|
private Func<DistroDefinition, VersionUpgradeResult> _tryUpgradeUnit;
|
||||||
private IReadOnlyList<DistroDefinition> _units = Array.Empty<DistroDefinition>();
|
private IReadOnlyList<DistroDefinition> _units = Array.Empty<DistroDefinition>();
|
||||||
private IReadOnlyList<LanguageCatalogEntry> _languages = LanguageCatalog.All;
|
private IReadOnlyList<LanguageCatalogEntry> _languages = LanguageCatalog.All;
|
||||||
private IReadOnlyList<CardEntry> _cards = Array.Empty<CardEntry>();
|
private IReadOnlyList<CardEntry> _cards = Array.Empty<CardEntry>();
|
||||||
@@ -35,14 +38,17 @@ namespace KernelPanic.UI
|
|||||||
|
|
||||||
public event Action ViewChanged;
|
public event Action ViewChanged;
|
||||||
|
|
||||||
public void Bind(VisualElement root, FontAsset artFont, LanguageDeckDatabase deckDatabase, CardDatabase cardsDatabase, PlayerCollection collection)
|
public void Bind(VisualElement root, FontAsset artFont, LanguageDeckDatabase deckDatabase, CardDatabase cardsDatabase, PlayerCollection collection, Func<int> getMergesBalance = null, Func<DistroDefinition, VersionUpgradeResult> tryUpgradeUnit = null)
|
||||||
{
|
{
|
||||||
_monospaceFont = artFont;
|
_monospaceFont = artFont;
|
||||||
_languageDeckDatabase = deckDatabase;
|
_languageDeckDatabase = deckDatabase;
|
||||||
_cardDatabase = cardsDatabase;
|
_cardDatabase = cardsDatabase;
|
||||||
_playerCollection = collection;
|
_playerCollection = collection;
|
||||||
|
_getMergesBalance = getMergesBalance;
|
||||||
|
_tryUpgradeUnit = tryUpgradeUnit;
|
||||||
_list = root.Q<VisualElement>("CollectionList");
|
_list = root.Q<VisualElement>("CollectionList");
|
||||||
_detail = root.Q<VisualElement>("CollectionDetail");
|
_detailScroll = root.Q<ScrollView>("CollectionDetail");
|
||||||
|
_detail = _detailScroll?.contentContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsCardSubview => _mode == CollectionMode.CardSubview;
|
public bool IsCardSubview => _mode == CollectionMode.CardSubview;
|
||||||
@@ -282,7 +288,79 @@ namespace KernelPanic.UI
|
|||||||
|
|
||||||
readout.Add(details);
|
readout.Add(details);
|
||||||
_detail.Add(readout);
|
_detail.Add(readout);
|
||||||
_detail.Add(BuildSubviewCommand($"> cat ~/units/{unit.Id}/cards", "exclusive packages", HasListableCards(unit), () => OpenUnitCards(unit), "no packages installed"));
|
AddVersionUpgradePanel(_detail, unit);
|
||||||
|
_detail.Add(BuildSubviewCommand($"cat ~/units/{unit.Id}/cards", "exclusive packages", HasListableCards(unit), () => OpenUnitCards(unit), "no packages installed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddVersionUpgradePanel(VisualElement target, DistroDefinition unit)
|
||||||
|
{
|
||||||
|
int version = _playerCollection == null ? 1 : Mathf.Clamp(_playerCollection.GetVersion(unit.Id), 1, GachaTuning.MaxVersion);
|
||||||
|
string release = DistroVersionCatalog.GetReleaseLabel(unit.Id, version);
|
||||||
|
int merges = _getMergesBalance?.Invoke() ?? 0;
|
||||||
|
|
||||||
|
VisualElement panel = new();
|
||||||
|
panel.AddToClassList("version-upgrade-panel");
|
||||||
|
|
||||||
|
VisualElement summary = new();
|
||||||
|
summary.AddToClassList("version-upgrade-summary");
|
||||||
|
|
||||||
|
Label current = new($"{DistroPresentation.DisplayName(unit)} {release} v{version}/{GachaTuning.MaxVersion}");
|
||||||
|
current.AddToClassList("version-upgrade-current");
|
||||||
|
summary.Add(current);
|
||||||
|
|
||||||
|
Label wallet = new(version < GachaTuning.MaxVersion ? $"merges {merges} / {GachaTuning.GetVersionUpgradeCost(version + 1)}" : $"merges {merges}");
|
||||||
|
wallet.AddToClassList("version-upgrade-wallet");
|
||||||
|
summary.Add(wallet);
|
||||||
|
panel.Add(summary);
|
||||||
|
|
||||||
|
if (version < GachaTuning.MaxVersion)
|
||||||
|
{
|
||||||
|
int targetVersion = version + 1;
|
||||||
|
int cost = GachaTuning.GetVersionUpgradeCost(targetVersion);
|
||||||
|
Label next = new($"next {DistroVersionCatalog.GetReleaseLabel(unit.Id, targetVersion)} - {DistroVersionCatalog.GetEffectSummary(unit.Id, targetVersion)}");
|
||||||
|
next.AddToClassList("version-upgrade-next");
|
||||||
|
panel.Add(next);
|
||||||
|
|
||||||
|
bool affordable = merges >= cost;
|
||||||
|
wallet.EnableInClassList(affordable ? "version-upgrade-wallet-affordable" : "version-upgrade-wallet-warning", true);
|
||||||
|
panel.Add(BuildCompactUpgradeCommand($"> apt full-upgrade ({cost} merges)", affordable ? "ready" : "insufficient merges", affordable && _tryUpgradeUnit != null, () => UpgradeUnit(unit)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Label next = new("latest release installed");
|
||||||
|
next.AddToClassList("version-upgrade-next");
|
||||||
|
panel.Add(next);
|
||||||
|
panel.Add(BuildCompactUpgradeCommand("> apt full-upgrade", "latest release", false, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
VisualElement path = new();
|
||||||
|
path.AddToClassList("version-path");
|
||||||
|
for (int i = 1; i <= GachaTuning.MaxVersion; i++)
|
||||||
|
{
|
||||||
|
string className = i <= version ? "version-path-owned" : i == version + 1 ? "version-path-next" : "version-path-locked";
|
||||||
|
Label step = new($"v{i} {DistroVersionCatalog.GetReleaseLabel(unit.Id, i)}");
|
||||||
|
step.AddToClassList("version-path-step");
|
||||||
|
step.AddToClassList(className);
|
||||||
|
path.Add(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.Add(path);
|
||||||
|
target.Add(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpgradeUnit(DistroDefinition unit)
|
||||||
|
{
|
||||||
|
if (unit == null || _tryUpgradeUnit == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VersionUpgradeResult result = _tryUpgradeUnit(unit);
|
||||||
|
RenderUnitDetail(unit);
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
_detail.Add(BuildDetailLine("upgrade", FormatUpgradeFailure(result.FailureReason)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderLanguageDetail(LanguageCatalogEntry language)
|
private void RenderLanguageDetail(LanguageCatalogEntry language)
|
||||||
@@ -310,7 +388,7 @@ namespace KernelPanic.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_detail.Add(BuildSubviewCommand($"> cat ~/lang/{GetLanguageId(language.Language)}/deck", "starter deck", true, () => OpenLanguageDeck(language), null));
|
_detail.Add(BuildSubviewCommand($"cat ~/lang/{GetLanguageId(language.Language)}/deck", "starter deck", true, () => OpenLanguageDeck(language), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenUnitCards(DistroDefinition unit)
|
private void OpenUnitCards(DistroDefinition unit)
|
||||||
@@ -463,6 +541,28 @@ namespace KernelPanic.UI
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static VisualElement BuildCompactUpgradeCommand(string command, string state, bool enabled, Action action)
|
||||||
|
{
|
||||||
|
VisualElement row = new();
|
||||||
|
row.AddToClassList("version-upgrade-command");
|
||||||
|
row.EnableInClassList("disabled-row", !enabled);
|
||||||
|
|
||||||
|
Label commandLabel = new(command);
|
||||||
|
commandLabel.AddToClassList("version-upgrade-command-text");
|
||||||
|
Label stateLabel = new(state);
|
||||||
|
stateLabel.AddToClassList("version-upgrade-command-state");
|
||||||
|
|
||||||
|
row.Add(commandLabel);
|
||||||
|
row.Add(stateLabel);
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
row.RegisterCallback<ClickEvent>(_ => action?.Invoke());
|
||||||
|
}
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
private static void AddPassiveDetails(VisualElement target, DistroDefinition unit)
|
private static void AddPassiveDetails(VisualElement target, DistroDefinition unit)
|
||||||
{
|
{
|
||||||
if (unit.Passive == null)
|
if (unit.Passive == null)
|
||||||
@@ -600,6 +700,17 @@ namespace KernelPanic.UI
|
|||||||
return card == null ? "--" : $"{card.Language} / {card.CycleCost}c";
|
return card == null ? "--" : $"{card.Language} / {card.CycleCost}c";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatUpgradeFailure(VersionUpgradeFailureReason reason)
|
||||||
|
{
|
||||||
|
return reason switch
|
||||||
|
{
|
||||||
|
VersionUpgradeFailureReason.NotOwned => "not owned",
|
||||||
|
VersionUpgradeFailureReason.MaxVersion => "latest release",
|
||||||
|
VersionUpgradeFailureReason.InsufficientMerges => "insufficient merges",
|
||||||
|
_ => "upgrade failed"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetLanguageId(Language language)
|
private static string GetLanguageId(Language language)
|
||||||
{
|
{
|
||||||
return language switch
|
return language switch
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace KernelPanic.UI
|
|||||||
public sealed class CombatSceneController : MonoBehaviour
|
public sealed class CombatSceneController : MonoBehaviour
|
||||||
{
|
{
|
||||||
private const string StyleResourcePath = "CombatScene";
|
private const string StyleResourcePath = "CombatScene";
|
||||||
|
private const string SharedScrollbarStyleResourcePath = "TerminalScrollbars";
|
||||||
private const string FilledCycle = "●";
|
private const string FilledCycle = "●";
|
||||||
private const string EmptyCycle = "○";
|
private const string EmptyCycle = "○";
|
||||||
|
|
||||||
@@ -48,8 +49,18 @@ namespace KernelPanic.UI
|
|||||||
private VisualElement handRow;
|
private VisualElement handRow;
|
||||||
private VisualElement turnResourceGrid;
|
private VisualElement turnResourceGrid;
|
||||||
private Label logLabel;
|
private Label logLabel;
|
||||||
|
private VisualElement feedbackLayer;
|
||||||
|
private VisualElement damageVignette;
|
||||||
private VisualElement overlay;
|
private VisualElement overlay;
|
||||||
private Color distroAccent;
|
private Color distroAccent;
|
||||||
|
private readonly Dictionary<CombatantState, VisualElement> combatantElements = new();
|
||||||
|
private readonly Dictionary<CardInstance, VisualElement> handCardElements = new();
|
||||||
|
private readonly Dictionary<CardInstance, VisualElement> queueChipElements = new();
|
||||||
|
private readonly Dictionary<CardInstance, VisualElement> stackChipElements = new();
|
||||||
|
private readonly Dictionary<CombatantState, int> previousUptime = new();
|
||||||
|
private readonly Dictionary<CombatantState, int> previousShield = new();
|
||||||
|
private readonly HashSet<CardInstance> knownHandCards = new();
|
||||||
|
private int queueCascadeIndex;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
@@ -64,9 +75,18 @@ namespace KernelPanic.UI
|
|||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
|
root?.EnableInClassList("reduced-motion", UIPreferences.ReducedMotion);
|
||||||
combatManager.StateChanged += Refresh;
|
combatManager.StateChanged += Refresh;
|
||||||
combatManager.CombatLog += HandleCombatLog;
|
combatManager.CombatLog += HandleCombatLog;
|
||||||
runManager.RepositoryChanged += Refresh;
|
runManager.RepositoryChanged += Refresh;
|
||||||
|
GameEvents.CardPlayed += HandleCardPlayed;
|
||||||
|
GameEvents.CardResolved += HandleCardResolved;
|
||||||
|
GameEvents.PhaseChanged += HandlePhaseChanged;
|
||||||
|
GameEvents.DamageDealt += HandleDamageDealt;
|
||||||
|
GameEvents.CombatantDefeated += HandleCombatantDefeated;
|
||||||
|
GameEvents.EnemyWouldAct += HandleEnemyWouldAct;
|
||||||
|
GameEvents.PlayerDamaged += HandlePlayerDamaged;
|
||||||
|
GameEvents.StatusApplied += HandleStatusApplied;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
@@ -101,6 +121,14 @@ namespace KernelPanic.UI
|
|||||||
combatManager.StateChanged -= Refresh;
|
combatManager.StateChanged -= Refresh;
|
||||||
combatManager.CombatLog -= HandleCombatLog;
|
combatManager.CombatLog -= HandleCombatLog;
|
||||||
runManager.RepositoryChanged -= Refresh;
|
runManager.RepositoryChanged -= Refresh;
|
||||||
|
GameEvents.CardPlayed -= HandleCardPlayed;
|
||||||
|
GameEvents.CardResolved -= HandleCardResolved;
|
||||||
|
GameEvents.PhaseChanged -= HandlePhaseChanged;
|
||||||
|
GameEvents.DamageDealt -= HandleDamageDealt;
|
||||||
|
GameEvents.CombatantDefeated -= HandleCombatantDefeated;
|
||||||
|
GameEvents.EnemyWouldAct -= HandleEnemyWouldAct;
|
||||||
|
GameEvents.PlayerDamaged -= HandlePlayerDamaged;
|
||||||
|
GameEvents.StatusApplied -= HandleStatusApplied;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryBuildRunConfig(out RunConfig config)
|
private bool TryBuildRunConfig(out RunConfig config)
|
||||||
@@ -140,7 +168,7 @@ namespace KernelPanic.UI
|
|||||||
|
|
||||||
AddLanguageDeck(Language.Python, startingDeck);
|
AddLanguageDeck(Language.Python, startingDeck);
|
||||||
AddLanguageDeck(Language.JavaScript, startingDeck);
|
AddLanguageDeck(Language.JavaScript, startingDeck);
|
||||||
config = new RunConfig(distro, Language.Python, Language.JavaScript, startingDeck, Environment.TickCount);
|
config = new RunConfig(distro, Language.Python, Language.JavaScript, startingDeck, Environment.TickCount, 1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +210,12 @@ namespace KernelPanic.UI
|
|||||||
{
|
{
|
||||||
root.styleSheets.Add(styleSheet);
|
root.styleSheets.Add(styleSheet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyleSheet scrollbarStyleSheet = Resources.Load<StyleSheet>(SharedScrollbarStyleResourcePath);
|
||||||
|
if (scrollbarStyleSheet != null)
|
||||||
|
{
|
||||||
|
root.styleSheets.Add(scrollbarStyleSheet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildLayout()
|
private void BuildLayout()
|
||||||
@@ -239,6 +273,16 @@ namespace KernelPanic.UI
|
|||||||
logLabel.AddToClassList("log-line");
|
logLabel.AddToClassList("log-line");
|
||||||
commandPanel.Add(logLabel);
|
commandPanel.Add(logLabel);
|
||||||
|
|
||||||
|
feedbackLayer = new();
|
||||||
|
feedbackLayer.AddToClassList("feedback-layer");
|
||||||
|
feedbackLayer.pickingMode = PickingMode.Ignore;
|
||||||
|
root.Add(feedbackLayer);
|
||||||
|
|
||||||
|
damageVignette = new();
|
||||||
|
damageVignette.AddToClassList("damage-vignette");
|
||||||
|
damageVignette.pickingMode = PickingMode.Ignore;
|
||||||
|
root.Add(damageVignette);
|
||||||
|
|
||||||
overlay = new();
|
overlay = new();
|
||||||
overlay.AddToClassList("overlay");
|
overlay.AddToClassList("overlay");
|
||||||
overlay.style.display = DisplayStyle.None;
|
overlay.style.display = DisplayStyle.None;
|
||||||
@@ -336,10 +380,13 @@ namespace KernelPanic.UI
|
|||||||
private void RefreshPlayer()
|
private void RefreshPlayer()
|
||||||
{
|
{
|
||||||
playerPanel.Clear();
|
playerPanel.Clear();
|
||||||
|
combatantElements.Remove(combatManager.PlayerState);
|
||||||
|
combatantElements[combatManager.PlayerState] = playerPanel;
|
||||||
playerPanel.Add(PanelHeader(DisplayName(runManager.CurrentConfig.Distro), "htop"));
|
playerPanel.Add(PanelHeader(DisplayName(runManager.CurrentConfig.Distro), "htop"));
|
||||||
ApplyAccent(playerPanel);
|
ApplyAccent(playerPanel);
|
||||||
|
|
||||||
CombatantState state = combatManager.PlayerState;
|
CombatantState state = combatManager.PlayerState;
|
||||||
|
DetectBeneficialResourceFeedback(state, playerPanel);
|
||||||
playerPanel.Add(MeterBlock("uptime", state.CurrentUptime, state.MaxUptime, MeterTone.Uptime));
|
playerPanel.Add(MeterBlock("uptime", state.CurrentUptime, state.MaxUptime, MeterTone.Uptime));
|
||||||
playerPanel.Add(MeterBlock("shield", state.Shield, Mathf.Max(1, state.Shield), state.Shield > 0 ? MeterTone.Beneficial : MeterTone.Muted));
|
playerPanel.Add(MeterBlock("shield", state.Shield, Mathf.Max(1, state.Shield), state.Shield > 0 ? MeterTone.Beneficial : MeterTone.Muted));
|
||||||
playerPanel.Add(CycleBlock(state.Cycles, state.MaxCycles));
|
playerPanel.Add(CycleBlock(state.Cycles, state.MaxCycles));
|
||||||
@@ -352,6 +399,8 @@ namespace KernelPanic.UI
|
|||||||
|
|
||||||
private void RefreshTracks()
|
private void RefreshTracks()
|
||||||
{
|
{
|
||||||
|
queueChipElements.Clear();
|
||||||
|
stackChipElements.Clear();
|
||||||
FillCardStrip(interpreterStrip, combatManager.InterpreterQueue.Cards, "queue empty");
|
FillCardStrip(interpreterStrip, combatManager.InterpreterQueue.Cards, "queue empty");
|
||||||
FillCardStrip(lazyStackPile, combatManager.LazyStack.Cards, "stack empty");
|
FillCardStrip(lazyStackPile, combatManager.LazyStack.Cards, "stack empty");
|
||||||
tokenArea.Clear();
|
tokenArea.Clear();
|
||||||
@@ -361,6 +410,12 @@ namespace KernelPanic.UI
|
|||||||
private void RefreshEnemies()
|
private void RefreshEnemies()
|
||||||
{
|
{
|
||||||
enemyRow.Clear();
|
enemyRow.Clear();
|
||||||
|
combatantElements.Clear();
|
||||||
|
if (combatManager.PlayerState != null)
|
||||||
|
{
|
||||||
|
combatantElements[combatManager.PlayerState] = playerPanel;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < combatManager.Enemies.Count; i++)
|
for (int i = 0; i < combatManager.Enemies.Count; i++)
|
||||||
{
|
{
|
||||||
int index = i;
|
int index = i;
|
||||||
@@ -377,6 +432,8 @@ namespace KernelPanic.UI
|
|||||||
Label name = new(enemy.Name);
|
Label name = new(enemy.Name);
|
||||||
name.AddToClassList("enemy-name");
|
name.AddToClassList("enemy-name");
|
||||||
card.Add(name);
|
card.Add(name);
|
||||||
|
combatantElements[enemy.State] = card;
|
||||||
|
DetectBeneficialResourceFeedback(enemy.State, card);
|
||||||
card.Add(MeterBlock("uptime", enemy.CurrentUptime, enemy.MaxUptime, MeterTone.Uptime));
|
card.Add(MeterBlock("uptime", enemy.CurrentUptime, enemy.MaxUptime, MeterTone.Uptime));
|
||||||
if (enemy.State.Shield > 0)
|
if (enemy.State.Shield > 0)
|
||||||
{
|
{
|
||||||
@@ -394,11 +451,27 @@ namespace KernelPanic.UI
|
|||||||
private void RefreshHand()
|
private void RefreshHand()
|
||||||
{
|
{
|
||||||
handRow.Clear();
|
handRow.Clear();
|
||||||
|
handCardElements.Clear();
|
||||||
|
HashSet<CardInstance> currentHandCards = new();
|
||||||
IReadOnlyList<CardInstance> hand = combatManager.HandController.Cards;
|
IReadOnlyList<CardInstance> hand = combatManager.HandController.Cards;
|
||||||
for (int i = 0; i < hand.Count; i++)
|
for (int i = 0; i < hand.Count; i++)
|
||||||
{
|
{
|
||||||
CardInstance card = hand[i];
|
CardInstance card = hand[i];
|
||||||
handRow.Add(CardFace(card));
|
currentHandCards.Add(card);
|
||||||
|
VisualElement face = CardFace(card);
|
||||||
|
handCardElements[card] = face;
|
||||||
|
if (!knownHandCards.Contains(card))
|
||||||
|
{
|
||||||
|
PlayElementBeat(face, "hand-card-drawn", 260);
|
||||||
|
}
|
||||||
|
|
||||||
|
handRow.Add(face);
|
||||||
|
}
|
||||||
|
|
||||||
|
knownHandCards.Clear();
|
||||||
|
foreach (CardInstance card in currentHandCards)
|
||||||
|
{
|
||||||
|
knownHandCards.Add(card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,7 +767,7 @@ namespace KernelPanic.UI
|
|||||||
|
|
||||||
private VisualElement CardFace(CardInstance card)
|
private VisualElement CardFace(CardInstance card)
|
||||||
{
|
{
|
||||||
Button button = new(() => combatManager.PlayCard(card));
|
Button button = new(() => PlayCardWithFeedback(card));
|
||||||
button.text = string.Empty;
|
button.text = string.Empty;
|
||||||
button.AddToClassList("hand-card");
|
button.AddToClassList("hand-card");
|
||||||
if (combatManager.PendingTargetCard == card)
|
if (combatManager.PendingTargetCard == card)
|
||||||
@@ -885,8 +958,348 @@ namespace KernelPanic.UI
|
|||||||
|
|
||||||
for (int i = 0; i < cards.Count; i++)
|
for (int i = 0; i < cards.Count; i++)
|
||||||
{
|
{
|
||||||
target.Add(CardChip(cards[i]));
|
VisualElement chip = CardChip(cards[i]);
|
||||||
|
if (target == interpreterStrip)
|
||||||
|
{
|
||||||
|
queueChipElements[cards[i]] = chip;
|
||||||
}
|
}
|
||||||
|
else if (target == lazyStackPile)
|
||||||
|
{
|
||||||
|
stackChipElements[cards[i]] = chip;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.Add(chip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool PlayCardWithFeedback(CardInstance card)
|
||||||
|
{
|
||||||
|
int cyclesBefore = combatManager.PlayerState == null ? 0 : combatManager.PlayerState.Cycles;
|
||||||
|
bool played = combatManager.PlayCard(card);
|
||||||
|
if (played)
|
||||||
|
{
|
||||||
|
int spent = Mathf.Max(0, cyclesBefore - (combatManager.PlayerState == null ? cyclesBefore : combatManager.PlayerState.Cycles));
|
||||||
|
if (spent > 0)
|
||||||
|
{
|
||||||
|
PlayElementBeat(turnResourceGrid, "cycles-spent-beat", 220);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (combatManager.PendingTargetCard != card && handCardElements.TryGetValue(card, out VisualElement face))
|
||||||
|
{
|
||||||
|
PlayElementBeat(face, "feedback-denied", 220);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCardPlayed(CardPlayedEvent payload)
|
||||||
|
{
|
||||||
|
if (payload.Card == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VisualElement source = handCardElements.TryGetValue(payload.Card, out VisualElement cardElement) ? cardElement : null;
|
||||||
|
VisualElement destination = payload.Track switch
|
||||||
|
{
|
||||||
|
ResolutionTrack.InterpreterQueue => interpreterStrip,
|
||||||
|
ResolutionTrack.LazyStack => lazyStackPile,
|
||||||
|
_ => FirstTargetElement(payload.Card)
|
||||||
|
};
|
||||||
|
|
||||||
|
FlyCardGhost(payload.Card, source, destination, payload.Track == ResolutionTrack.Native ? "feedback-card-strike" : "feedback-card-route");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCardResolved(CardResolvedEvent payload)
|
||||||
|
{
|
||||||
|
if (payload.Card == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.Track == ResolutionTrack.InterpreterQueue)
|
||||||
|
{
|
||||||
|
int delay = UIPreferences.ReducedMotion ? 0 : Mathf.Min(queueCascadeIndex * 120, 600);
|
||||||
|
queueCascadeIndex++;
|
||||||
|
ScheduleFeedback(() =>
|
||||||
|
{
|
||||||
|
VisualElement chip = queueChipElements.TryGetValue(payload.Card, out VisualElement queuedChip) ? queuedChip : interpreterStrip;
|
||||||
|
PlayElementBeat(chip, "queue-chip-resolve", 260);
|
||||||
|
FlyCardGhost(payload.Card, chip, turnResourceGrid, "feedback-card-discard");
|
||||||
|
}, delay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.Track == ResolutionTrack.LazyStack)
|
||||||
|
{
|
||||||
|
VisualElement chip = stackChipElements.TryGetValue(payload.Card, out VisualElement stackChip) ? stackChip : lazyStackPile;
|
||||||
|
PlayElementBeat(chip, "stack-chip-resolve", 320);
|
||||||
|
FlyCardGhost(payload.Card, chip, turnResourceGrid, "feedback-card-discard");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePhaseChanged(PhaseChangedEvent payload)
|
||||||
|
{
|
||||||
|
if (payload.NextPhase == TurnPhase.Interpret)
|
||||||
|
{
|
||||||
|
queueCascadeIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduleFeedback(() => PlayElementBeat(phaseLabel, "phase-pulse", 260), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDamageDealt(DamageDealtEvent payload)
|
||||||
|
{
|
||||||
|
ScheduleFeedback(() =>
|
||||||
|
{
|
||||||
|
bool isPlayer = payload.Target == combatManager.PlayerState;
|
||||||
|
VisualElement target = CombatantElement(payload.Target);
|
||||||
|
string text = payload.Amount <= 0 ? "blocked" : $"-{Mathf.RoundToInt(payload.Amount)}";
|
||||||
|
string tone = payload.Amount <= 0 ? "float-muted" : "float-damage";
|
||||||
|
if (payload.Amount >= 8)
|
||||||
|
{
|
||||||
|
tone += " float-large";
|
||||||
|
}
|
||||||
|
|
||||||
|
SpawnFloatingText(target, text, tone);
|
||||||
|
PlayElementBeat(target, payload.Amount <= 0 ? "feedback-block" : "feedback-hit", 240);
|
||||||
|
if (isPlayer && payload.Amount > 0)
|
||||||
|
{
|
||||||
|
FlashDamageVignette();
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCombatantDefeated(CombatantDefeatedEvent payload)
|
||||||
|
{
|
||||||
|
ScheduleFeedback(() =>
|
||||||
|
{
|
||||||
|
VisualElement target = CombatantElement(payload.Combatant);
|
||||||
|
SpawnFloatingText(target, "killed", "float-kill float-large");
|
||||||
|
SpawnDeathGhost(target);
|
||||||
|
PlayElementBeat(target, "feedback-killed", 260);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleEnemyWouldAct(EnemyWouldActEvent payload)
|
||||||
|
{
|
||||||
|
ScheduleFeedback(() => PlayElementBeat(CombatantElement(payload.Enemy?.State), "enemy-acting", 260), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePlayerDamaged(PlayerDamagedEvent payload)
|
||||||
|
{
|
||||||
|
if (payload.Amount > 0)
|
||||||
|
{
|
||||||
|
ScheduleFeedback(FlashDamageVignette, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleStatusApplied(StatusAppliedEvent payload)
|
||||||
|
{
|
||||||
|
ScheduleFeedback(() =>
|
||||||
|
{
|
||||||
|
StatusDescriptor descriptor = StatusEffectController.GetDescriptor(payload.StatusType);
|
||||||
|
SpawnFloatingText(CombatantElement(payload.Target), $"{descriptor.IconKey} x{payload.Stacks}", descriptor.IsBeneficial ? "float-heal" : "float-status");
|
||||||
|
PlayElementBeat(CombatantElement(payload.Target), descriptor.IsBeneficial ? "feedback-boost" : "feedback-status", 220);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetectBeneficialResourceFeedback(CombatantState state, VisualElement element)
|
||||||
|
{
|
||||||
|
if (state == null || element == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousUptime.TryGetValue(state, out int oldUptime) && state.CurrentUptime > oldUptime)
|
||||||
|
{
|
||||||
|
SpawnFloatingText(element, $"+{state.CurrentUptime - oldUptime}", "float-heal");
|
||||||
|
PlayElementBeat(element, "feedback-boost", 220);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousShield.TryGetValue(state, out int oldShield) && state.Shield > oldShield)
|
||||||
|
{
|
||||||
|
SpawnFloatingText(element, $"+{state.Shield - oldShield} shield", "float-heal");
|
||||||
|
PlayElementBeat(element, "feedback-block", 220);
|
||||||
|
}
|
||||||
|
|
||||||
|
previousUptime[state] = state.CurrentUptime;
|
||||||
|
previousShield[state] = state.Shield;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VisualElement CombatantElement(CombatantState state)
|
||||||
|
{
|
||||||
|
if (state != null && combatantElements.TryGetValue(state, out VisualElement element))
|
||||||
|
{
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state == combatManager.PlayerState ? playerPanel : enemyRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VisualElement FirstTargetElement(CardInstance card)
|
||||||
|
{
|
||||||
|
if (card?.TargetSnapshot == null)
|
||||||
|
{
|
||||||
|
return enemyRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < card.TargetSnapshot.Count; i++)
|
||||||
|
{
|
||||||
|
VisualElement element = CombatantElement(card.TargetSnapshot[i]);
|
||||||
|
if (element != null)
|
||||||
|
{
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enemyRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayElementBeat(VisualElement element, string className, int durationMs)
|
||||||
|
{
|
||||||
|
if (element == null || string.IsNullOrWhiteSpace(className))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.RemoveFromClassList(className);
|
||||||
|
element.AddToClassList(className);
|
||||||
|
ScheduleFeedback(() => element.RemoveFromClassList(className), UIPreferences.ReducedMotion ? 80 : durationMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpawnFloatingText(VisualElement anchor, string text, string className)
|
||||||
|
{
|
||||||
|
if (feedbackLayer == null || anchor == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect anchorRect = anchor.worldBound;
|
||||||
|
Rect rootRect = root.worldBound;
|
||||||
|
Label label = new(text);
|
||||||
|
label.AddToClassList("float-number");
|
||||||
|
if (!string.IsNullOrWhiteSpace(className))
|
||||||
|
{
|
||||||
|
string[] classes = className.Split(' ');
|
||||||
|
for (int i = 0; i < classes.Length; i++)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(classes[i]))
|
||||||
|
{
|
||||||
|
label.AddToClassList(classes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label.style.left = anchorRect.center.x - rootRect.x - 30f;
|
||||||
|
label.style.top = anchorRect.center.y - rootRect.y - 16f;
|
||||||
|
feedbackLayer.Add(label);
|
||||||
|
|
||||||
|
if (UIPreferences.ReducedMotion)
|
||||||
|
{
|
||||||
|
label.AddToClassList("float-static");
|
||||||
|
ScheduleFeedback(() => label.RemoveFromHierarchy(), 420);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduleFeedback(() =>
|
||||||
|
{
|
||||||
|
label.style.top = anchorRect.center.y - rootRect.y - 42f;
|
||||||
|
label.style.opacity = 0f;
|
||||||
|
}, 20);
|
||||||
|
ScheduleFeedback(() => label.RemoveFromHierarchy(), 520);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlyCardGhost(CardInstance card, VisualElement source, VisualElement destination, string className)
|
||||||
|
{
|
||||||
|
if (feedbackLayer == null || card == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect rootRect = root.worldBound;
|
||||||
|
Rect sourceRect = source == null ? new Rect(rootRect.center.x - 70f, rootRect.center.y - 50f, 140f, 110f) : source.worldBound;
|
||||||
|
Rect destinationRect = destination == null ? sourceRect : destination.worldBound;
|
||||||
|
|
||||||
|
VisualElement ghost = CardFaceView(card);
|
||||||
|
ghost.AddToClassList("feedback-card-ghost");
|
||||||
|
ghost.AddToClassList(className);
|
||||||
|
ghost.style.position = Position.Absolute;
|
||||||
|
ghost.style.left = sourceRect.x - rootRect.x;
|
||||||
|
ghost.style.top = sourceRect.y - rootRect.y;
|
||||||
|
ghost.style.width = Mathf.Max(110f, sourceRect.width);
|
||||||
|
ghost.style.height = Mathf.Max(94f, sourceRect.height);
|
||||||
|
feedbackLayer.Add(ghost);
|
||||||
|
|
||||||
|
if (UIPreferences.ReducedMotion)
|
||||||
|
{
|
||||||
|
ghost.AddToClassList("feedback-card-instant");
|
||||||
|
ScheduleFeedback(() => ghost.RemoveFromHierarchy(), 140);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduleFeedback(() =>
|
||||||
|
{
|
||||||
|
ghost.style.left = destinationRect.center.x - rootRect.x - (sourceRect.width * 0.5f);
|
||||||
|
ghost.style.top = destinationRect.center.y - rootRect.y - (sourceRect.height * 0.5f);
|
||||||
|
ghost.style.opacity = 0f;
|
||||||
|
}, 20);
|
||||||
|
ScheduleFeedback(() => ghost.RemoveFromHierarchy(), 360);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpawnDeathGhost(VisualElement source)
|
||||||
|
{
|
||||||
|
if (feedbackLayer == null || source == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect rootRect = root.worldBound;
|
||||||
|
Rect sourceRect = source.worldBound;
|
||||||
|
VisualElement ghost = new();
|
||||||
|
ghost.AddToClassList("death-ghost");
|
||||||
|
ghost.style.position = Position.Absolute;
|
||||||
|
ghost.style.left = sourceRect.x - rootRect.x;
|
||||||
|
ghost.style.top = sourceRect.y - rootRect.y;
|
||||||
|
ghost.style.width = sourceRect.width;
|
||||||
|
ghost.style.height = sourceRect.height;
|
||||||
|
feedbackLayer.Add(ghost);
|
||||||
|
|
||||||
|
if (UIPreferences.ReducedMotion)
|
||||||
|
{
|
||||||
|
ScheduleFeedback(() => ghost.RemoveFromHierarchy(), 180);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduleFeedback(() =>
|
||||||
|
{
|
||||||
|
ghost.style.opacity = 0f;
|
||||||
|
ghost.style.scale = new Scale(new Vector3(0.92f, 0.92f, 1f));
|
||||||
|
}, 20);
|
||||||
|
ScheduleFeedback(() => ghost.RemoveFromHierarchy(), 360);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlashDamageVignette()
|
||||||
|
{
|
||||||
|
if (damageVignette == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayElementBeat(damageVignette, "damage-vignette-on", UIPreferences.ReducedMotion ? 120 : 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScheduleFeedback(Action action, int delayMs)
|
||||||
|
{
|
||||||
|
if (action == null || root == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.schedule.Execute(action).StartingIn(Mathf.Max(0, delayMs));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleCombatLog(string message)
|
private void HandleCombatLog(string message)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace KernelPanic.UI
|
|||||||
private GachaService _gachaService;
|
private GachaService _gachaService;
|
||||||
private PlayerCollection _playerCollection;
|
private PlayerCollection _playerCollection;
|
||||||
private EntropyWallet _wallet;
|
private EntropyWallet _wallet;
|
||||||
|
private Func<IReadOnlyList<DistroDefinition>, PullResolutionResult> _resolvePulledDistros;
|
||||||
private Action _onChanged;
|
private Action _onChanged;
|
||||||
private Action _requestRootCreditExchange;
|
private Action _requestRootCreditExchange;
|
||||||
private int _selectedBannerIndex;
|
private int _selectedBannerIndex;
|
||||||
@@ -40,13 +41,14 @@ namespace KernelPanic.UI
|
|||||||
private string _resultBannerId;
|
private string _resultBannerId;
|
||||||
private string _resultText;
|
private string _resultText;
|
||||||
|
|
||||||
public void Bind(VisualElement root, DistroDatabase database, FontAsset artFont, GachaService service, PlayerCollection collection, EntropyWallet entropyWallet, Action changedCallback, Action rootCreditExchangeCallback)
|
public void Bind(VisualElement root, DistroDatabase database, FontAsset artFont, GachaService service, PlayerCollection collection, EntropyWallet entropyWallet, Func<IReadOnlyList<DistroDefinition>, PullResolutionResult> pullResolver, Action changedCallback, Action rootCreditExchangeCallback)
|
||||||
{
|
{
|
||||||
_distroDatabase = database;
|
_distroDatabase = database;
|
||||||
_monospaceFont = artFont;
|
_monospaceFont = artFont;
|
||||||
_gachaService = service;
|
_gachaService = service;
|
||||||
_playerCollection = collection;
|
_playerCollection = collection;
|
||||||
_wallet = entropyWallet;
|
_wallet = entropyWallet;
|
||||||
|
_resolvePulledDistros = pullResolver;
|
||||||
_onChanged = changedCallback;
|
_onChanged = changedCallback;
|
||||||
_requestRootCreditExchange = rootCreditExchangeCallback;
|
_requestRootCreditExchange = rootCreditExchangeCallback;
|
||||||
_bannerList = root.Q<VisualElement>("GachaBannerList");
|
_bannerList = root.Q<VisualElement>("GachaBannerList");
|
||||||
@@ -252,18 +254,30 @@ namespace KernelPanic.UI
|
|||||||
lines.Add($"entropy fallback: spent {result.EntropySpent} entropy");
|
lines.Add($"entropy fallback: spent {result.EntropySpent} entropy");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<DistroDefinition> distroRewards = new();
|
||||||
|
for (int i = 0; i < result.Rewards.Count; i++)
|
||||||
|
{
|
||||||
|
if (result.Rewards[i].Distro != null)
|
||||||
|
{
|
||||||
|
distroRewards.Add(result.Rewards[i].Distro);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PullResolutionResult resolution = _resolvePulledDistros?.Invoke(distroRewards);
|
||||||
|
int distroOutcomeIndex = 0;
|
||||||
for (int i = 0; i < result.Rewards.Count; i++)
|
for (int i = 0; i < result.Rewards.Count; i++)
|
||||||
{
|
{
|
||||||
GachaReward reward = result.Rewards[i];
|
GachaReward reward = result.Rewards[i];
|
||||||
bool duplicate = reward.Distro != null && _playerCollection.GetOwnedCount(reward.Distro.Id) > 0;
|
PullResolutionOutcome? outcome = null;
|
||||||
if (reward.Distro != null)
|
if (reward.Distro != null && resolution != null && distroOutcomeIndex < resolution.Outcomes.Count)
|
||||||
{
|
{
|
||||||
_playerCollection.Add(reward.Distro);
|
outcome = resolution.Outcomes[distroOutcomeIndex];
|
||||||
|
distroOutcomeIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
string suffix = reward.Guaranteed ? " guaranteed" : reward.PityTriggered ? " pity" : string.Empty;
|
string suffix = reward.Guaranteed ? " guaranteed" : reward.PityTriggered ? " pity" : string.Empty;
|
||||||
string duplicateText = duplicate ? " duplicate" : string.Empty;
|
string outcomeText = FormatPullOutcome(outcome);
|
||||||
lines.Add($"{i + 1:00}: {reward.StarRating}-star {reward.DisplayName}{suffix}{duplicateText}");
|
lines.Add($"{i + 1:00}: {reward.StarRating}-star {reward.DisplayName}{suffix}{outcomeText}");
|
||||||
}
|
}
|
||||||
|
|
||||||
_resultBannerId = bannerId;
|
_resultBannerId = bannerId;
|
||||||
@@ -272,6 +286,23 @@ namespace KernelPanic.UI
|
|||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatPullOutcome(PullResolutionOutcome? outcome)
|
||||||
|
{
|
||||||
|
if (!outcome.HasValue)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
PullResolutionOutcome value = outcome.Value;
|
||||||
|
return value.Kind switch
|
||||||
|
{
|
||||||
|
PullOutcomeKind.Granted => " granted",
|
||||||
|
PullOutcomeKind.Dupe => $" duplicate +{value.MergesAwarded} merges",
|
||||||
|
PullOutcomeKind.DupeOverflow => $" max-version duplicate +{value.MergesAwarded} merges",
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void RequestPullSelectedBanner(int pullCount)
|
private void RequestPullSelectedBanner(int pullCount)
|
||||||
{
|
{
|
||||||
string bannerId = _bannerIds[_selectedBannerIndex];
|
string bannerId = _bannerIds[_selectedBannerIndex];
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace KernelPanic.UI
|
|||||||
private const string HiddenClassName = "hidden";
|
private const string HiddenClassName = "hidden";
|
||||||
private const string SelectedClassName = "selected";
|
private const string SelectedClassName = "selected";
|
||||||
private const string CursorOnClassName = "cursor-on";
|
private const string CursorOnClassName = "cursor-on";
|
||||||
|
private const string SharedScrollbarStyleResourcePath = "TerminalScrollbars";
|
||||||
private const float BootIntroSeconds = 1.5f;
|
private const float BootIntroSeconds = 1.5f;
|
||||||
private static bool _bootIntroPlayed;
|
private static bool _bootIntroPlayed;
|
||||||
|
|
||||||
@@ -133,13 +134,14 @@ namespace KernelPanic.UI
|
|||||||
_cardLoadout = new CardLoadout(_playerCollection.OwnedUnits);
|
_cardLoadout = new CardLoadout(_playerCollection.OwnedUnits);
|
||||||
Initialize(new EntropyWallet()); // TODO: Replace with persistent wallet service composition.
|
Initialize(new EntropyWallet()); // TODO: Replace with persistent wallet service composition.
|
||||||
BindElements();
|
BindElements();
|
||||||
|
LoadSharedStyles();
|
||||||
BindCommandEntries();
|
BindCommandEntries();
|
||||||
RegisterCommandEntryCallbacks();
|
RegisterCommandEntryCallbacks();
|
||||||
featuredUnitPanel.Bind(_root, monospaceFont);
|
featuredUnitPanel.Bind(_root, monospaceFont);
|
||||||
collectionScreen.Bind(_root, monospaceFont, languageDeckDatabase, cardDatabase, _playerCollection);
|
collectionScreen.Bind(_root, monospaceFont, languageDeckDatabase, cardDatabase, _playerCollection, GetMergesBalance, UpgradeCollectionUnit);
|
||||||
BindScreenFrames();
|
BindScreenFrames();
|
||||||
starterSelection.Bind(_root, distroDatabase, HandleStarterConfirmed);
|
starterSelection.Bind(_root, distroDatabase, HandleStarterConfirmed);
|
||||||
gachaScreen.Bind(_root, distroDatabase, monospaceFont, _gachaService, _playerCollection, _wallet, HandleMetaStateChanged, OpenRootCreditExchange);
|
gachaScreen.Bind(_root, distroDatabase, monospaceFont, _gachaService, _playerCollection, _wallet, ResolvePulledDistros, HandleMetaStateChanged, OpenRootCreditExchange);
|
||||||
LoadMetaState();
|
LoadMetaState();
|
||||||
_playerCollection.Changed += HandleMetaStateChanged;
|
_playerCollection.Changed += HandleMetaStateChanged;
|
||||||
_gachaService.Changed += HandleMetaStateChanged;
|
_gachaService.Changed += HandleMetaStateChanged;
|
||||||
@@ -221,6 +223,15 @@ namespace KernelPanic.UI
|
|||||||
_collectionLanguagesButton = _root.Q<Button>("CollectionLanguagesButton");
|
_collectionLanguagesButton = _root.Q<Button>("CollectionLanguagesButton");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadSharedStyles()
|
||||||
|
{
|
||||||
|
StyleSheet scrollbarStyleSheet = Resources.Load<StyleSheet>(SharedScrollbarStyleResourcePath);
|
||||||
|
if (scrollbarStyleSheet != null)
|
||||||
|
{
|
||||||
|
_root.styleSheets.Add(scrollbarStyleSheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void BindScreenFrames()
|
private void BindScreenFrames()
|
||||||
{
|
{
|
||||||
_runSetupFrame.Bind(_runSetupPanel, "$ ./start_run --configure", "[esc] back [arrows] navigate [enter] select [b] boot run", ShowMainMenu);
|
_runSetupFrame.Bind(_runSetupPanel, "$ ./start_run --configure", "[esc] back [arrows] navigate [enter] select [b] boot run", ShowMainMenu);
|
||||||
@@ -554,12 +565,13 @@ namespace KernelPanic.UI
|
|||||||
_saveData.EnsureLists();
|
_saveData.EnsureLists();
|
||||||
_wallet.SetBalance(_saveData.entropyBalance);
|
_wallet.SetBalance(_saveData.entropyBalance);
|
||||||
|
|
||||||
for (int i = 0; i < _saveData.ownedUnitIds.Count; i++)
|
for (int i = 0; i < _saveData.ownedUnits.Count; i++)
|
||||||
{
|
{
|
||||||
DistroDefinition unit = ResolveSavedDistro(_saveData.ownedUnitIds[i]);
|
OwnedUnitSaveEntry entry = _saveData.ownedUnits[i];
|
||||||
|
DistroDefinition unit = ResolveSavedDistro(entry?.id);
|
||||||
if (unit != null)
|
if (unit != null)
|
||||||
{
|
{
|
||||||
_playerCollection.Add(unit);
|
_playerCollection.Add(unit, entry.version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,7 +636,7 @@ namespace KernelPanic.UI
|
|||||||
|
|
||||||
private void HandleStarterConfirmed(DistroDefinition picked, IReadOnlyList<DistroDefinition> remaining)
|
private void HandleStarterConfirmed(DistroDefinition picked, IReadOnlyList<DistroDefinition> remaining)
|
||||||
{
|
{
|
||||||
_playerCollection.Add(picked);
|
_playerCollection.Add(picked, 1);
|
||||||
AddAllFourStarDistrosToBeginnerBannerPool();
|
AddAllFourStarDistrosToBeginnerBannerPool();
|
||||||
_gachaService.SetBeginnerGuaranteedDistros(remaining);
|
_gachaService.SetBeginnerGuaranteedDistros(remaining);
|
||||||
_saveData.starterChosen = true;
|
_saveData.starterChosen = true;
|
||||||
@@ -639,6 +651,7 @@ namespace KernelPanic.UI
|
|||||||
_saveData ??= SaveData.CreateDefault();
|
_saveData ??= SaveData.CreateDefault();
|
||||||
_saveData.EnsureLists();
|
_saveData.EnsureLists();
|
||||||
_saveData.entropyBalance = _wallet == null ? 0 : _wallet.Balance;
|
_saveData.entropyBalance = _wallet == null ? 0 : _wallet.Balance;
|
||||||
|
_saveData.ownedUnits.Clear();
|
||||||
_saveData.ownedUnitIds.Clear();
|
_saveData.ownedUnitIds.Clear();
|
||||||
_saveData.bannerPoolIds.Clear();
|
_saveData.bannerPoolIds.Clear();
|
||||||
_gachaService.WriteProgress(_saveData);
|
_gachaService.WriteProgress(_saveData);
|
||||||
@@ -648,7 +661,11 @@ namespace KernelPanic.UI
|
|||||||
DistroDefinition unit = _playerCollection.OwnedUnits[i];
|
DistroDefinition unit = _playerCollection.OwnedUnits[i];
|
||||||
if (unit != null && !string.IsNullOrWhiteSpace(unit.Id))
|
if (unit != null && !string.IsNullOrWhiteSpace(unit.Id))
|
||||||
{
|
{
|
||||||
_saveData.ownedUnitIds.Add(unit.Id);
|
_saveData.ownedUnits.Add(new OwnedUnitSaveEntry
|
||||||
|
{
|
||||||
|
id = unit.Id,
|
||||||
|
version = Mathf.Clamp(_playerCollection.GetVersion(unit.Id), 1, GachaTuning.MaxVersion)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,6 +681,38 @@ namespace KernelPanic.UI
|
|||||||
_saveService.Save(_saveData);
|
_saveService.Save(_saveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PullResolutionResult ResolvePulledDistros(IReadOnlyList<DistroDefinition> pulledDistros)
|
||||||
|
{
|
||||||
|
PullResolutionContext context = new(_saveData, _playerCollection, distroDatabase, _playerCollection.FeaturedUnit?.Id);
|
||||||
|
return PullResolver.Resolve(pulledDistros, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetMergesBalance()
|
||||||
|
{
|
||||||
|
_saveData ??= SaveData.CreateDefault();
|
||||||
|
_saveData.EnsureLists();
|
||||||
|
return _saveData.merges;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VersionUpgradeResult UpgradeCollectionUnit(DistroDefinition unit)
|
||||||
|
{
|
||||||
|
if (unit == null)
|
||||||
|
{
|
||||||
|
return VersionUpgradeResult.Failed(null, VersionUpgradeFailureReason.NotOwned);
|
||||||
|
}
|
||||||
|
|
||||||
|
VersionUpgradeResult result = VersionUpgrader.TryUpgrade(unit.Id, _saveData, _playerCollection);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
SaveCurrentState();
|
||||||
|
featuredUnitPanel.Refresh(_playerCollection.OwnedUnits, _playerCollection.FeaturedUnit);
|
||||||
|
collectionScreen.RefreshUnits(_playerCollection.OwnedUnits);
|
||||||
|
RefreshCurrencyReadouts();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private void SaveLastRunLoadout(DistroDefinition unit, IReadOnlyList<string> cardIds)
|
private void SaveLastRunLoadout(DistroDefinition unit, IReadOnlyList<string> cardIds)
|
||||||
{
|
{
|
||||||
_saveData ??= SaveData.CreateDefault();
|
_saveData ??= SaveData.CreateDefault();
|
||||||
@@ -708,7 +757,7 @@ namespace KernelPanic.UI
|
|||||||
_rootCreditsToEntropyButton?.SetEnabled(true);
|
_rootCreditsToEntropyButton?.SetEnabled(true);
|
||||||
bool showPullTokens = IsGachaVisible();
|
bool showPullTokens = IsGachaVisible();
|
||||||
_pullTokensLabel.text = showPullTokens
|
_pullTokensLabel.text = showPullTokens
|
||||||
? $"bandwidth={_gachaService.PullTokens} compute-credits={_gachaService.LimitedPullTokens}"
|
? $"bandwidth={_gachaService.PullTokens} compute-credits={_gachaService.LimitedPullTokens} merges={_saveData.merges}"
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
_pullTokensLabel.EnableInClassList(HiddenClassName, !showPullTokens);
|
_pullTokensLabel.EnableInClassList(HiddenClassName, !showPullTokens);
|
||||||
}
|
}
|
||||||
@@ -1452,7 +1501,7 @@ namespace KernelPanic.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
SaveLastRunLoadout(unit, equippedCardIds);
|
SaveLastRunLoadout(unit, equippedCardIds);
|
||||||
RunContext.Set(unit, BuildEquippedCardDefinitions(unit, equippedCardIds), _selectedRunLanguages[0], _selectedRunLanguages[1]);
|
RunContext.Set(unit, BuildEquippedCardDefinitions(unit, equippedCardIds), _selectedRunLanguages[0], _selectedRunLanguages[1], _playerCollection.GetVersion(unit.Id));
|
||||||
SceneLoader.LoadGame();
|
SceneLoader.LoadGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement name="CollectionContent" class="collection-shell">
|
<ui:VisualElement name="CollectionContent" class="collection-shell">
|
||||||
<ui:ScrollView name="CollectionList" class="collection-list" />
|
<ui:ScrollView name="CollectionList" class="collection-list" />
|
||||||
<ui:VisualElement name="CollectionDetail" class="collection-detail" />
|
<ui:ScrollView name="CollectionDetail" class="collection-detail" />
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement class="screen-frame-footer">
|
<ui:VisualElement class="screen-frame-footer">
|
||||||
|
|||||||
+132
-23
@@ -8,6 +8,9 @@
|
|||||||
--kp-border: #1d3b2a;
|
--kp-border: #1d3b2a;
|
||||||
--kp-hover: #102018;
|
--kp-hover: #102018;
|
||||||
--kp-amber: #ffbf5c;
|
--kp-amber: #ffbf5c;
|
||||||
|
--kp-scroll-track: var(--kp-panel);
|
||||||
|
--kp-scroll-thumb: var(--kp-muted);
|
||||||
|
--kp-scroll-thumb-hover: var(--kp-primary-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-root {
|
.terminal-root {
|
||||||
@@ -774,8 +777,8 @@ Button {
|
|||||||
.collection-detail {
|
.collection-detail {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
min-height: 0;
|
||||||
padding: 16px;
|
padding: 0;
|
||||||
border-left-width: 1px;
|
border-left-width: 1px;
|
||||||
border-right-width: 1px;
|
border-right-width: 1px;
|
||||||
border-top-width: 1px;
|
border-top-width: 1px;
|
||||||
@@ -787,14 +790,141 @@ Button {
|
|||||||
background-color: var(--kp-panel);
|
background-color: var(--kp-panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collection-detail .unity-scroll-view__content-container {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.collection-detail-readout {
|
.collection-detail-readout {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
flex-shrink: 0;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-top-color: var(--kp-border);
|
||||||
|
border-bottom-color: var(--kp-border);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-upgrade-panel {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px 10px;
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-right-width: 1px;
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-left-color: var(--kp-border);
|
||||||
|
border-right-color: var(--kp-border);
|
||||||
|
border-top-color: var(--kp-border);
|
||||||
|
border-bottom-color: var(--kp-border);
|
||||||
|
background-color: var(--kp-bg-solid);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-upgrade-summary {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-upgrade-current {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
color: var(--kp-primary);
|
||||||
|
-unity-font-style: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-upgrade-wallet {
|
||||||
|
color: var(--kp-muted);
|
||||||
|
min-width: 110px;
|
||||||
|
-unity-text-align: middle-right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-upgrade-wallet-affordable {
|
||||||
|
color: var(--kp-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-upgrade-wallet-warning {
|
||||||
|
color: var(--kp-amber);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-upgrade-next {
|
||||||
|
color: var(--kp-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
white-space: normal;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-upgrade-command {
|
||||||
|
min-height: 26px;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
border-left-width: 2px;
|
||||||
|
border-left-color: var(--kp-primary);
|
||||||
|
background-color: var(--kp-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-upgrade-command:hover {
|
||||||
|
background-color: var(--kp-panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-upgrade-command-text {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
color: var(--kp-primary);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-upgrade-command-state {
|
||||||
|
color: var(--kp-muted);
|
||||||
|
min-width: 120px;
|
||||||
|
-unity-text-align: middle-right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-path {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-path-step {
|
||||||
|
color: var(--kp-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 14px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-path-owned {
|
||||||
|
color: var(--kp-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-path-next {
|
||||||
|
color: var(--kp-amber);
|
||||||
|
-unity-font-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-path-locked {
|
||||||
|
color: var(--kp-muted);
|
||||||
|
opacity: 0.65;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collection-detail-values {
|
.collection-detail-values {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
padding-left: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collection-detail .kv-value {
|
.collection-detail .kv-value {
|
||||||
@@ -1039,27 +1169,6 @@ Button {
|
|||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView .unity-scroll-view__vertical-scroller,
|
|
||||||
ScrollView .unity-scroll-view__horizontal-scroller {
|
|
||||||
width: 6px;
|
|
||||||
background-color: var(--kp-panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollView .unity-scroller__low-button,
|
|
||||||
ScrollView .unity-scroller__high-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollView .unity-base-slider__tracker {
|
|
||||||
background-color: var(--kp-panel);
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollView .unity-base-slider__dragger {
|
|
||||||
background-color: var(--kp-muted);
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user