Merge remote-tracking branch 'origin/main'

This commit is contained in:
2026-07-10 11:02:33 +02:00
33 changed files with 1025 additions and 136 deletions
@@ -11,30 +11,40 @@
</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/Scripts/Combat/EnemyArchetype.cs" afterDir="false" /> <change afterPath="$PROJECT_DIR$/Assets/Data/Cards/Arch/AurHelper.asset" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Assets/Data/Cards/Arch/AurHelper.asset.meta" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Assets/Data/Cards/Arch/ConsultWiki.asset" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Assets/Data/Cards/Arch/ConsultWiki.asset.meta" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Assets/Data/Cards/Arch/Makepkg.asset" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Assets/Data/Cards/Arch/Makepkg.asset.meta" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Assets/Data/Cards/Arch/MinimalInstall.asset" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Assets/Data/Cards/Arch/MinimalInstall.asset.meta" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Assets/Data/Cards/Arch/PacmanSyu.asset" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Assets/Data/Cards/Arch/PacmanSyu.asset.meta" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.linuxdistrosbuttheyarebreedable/.idea/indexLayout.xml" beforeDir="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/Data/Cards/Fedora/DnfUpdate.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Fedora/DnfUpdate.asset" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Malloc.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Malloc.asset" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Malloc.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Malloc.asset" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Pointer.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Pointer.asset" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Pointer.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Pointer.asset" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Printf.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Printf.asset" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Printf.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Printf.asset" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Languages/Java/JavaNewObject.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Languages/Java/JavaNewObject.asset" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Languages/Java/JavaPublicStaticMain.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Languages/Java/JavaPublicStaticMain.asset" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Languages/Java/JavaSystemOutPrintln.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Languages/Java/JavaSystemOutPrintln.asset" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Shop/Free.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Shop/Free.asset" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Shop/Free.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Shop/Free.asset" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Shop/KernelExploit.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Shop/KernelExploit.asset" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Shop/KernelExploit.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Shop/KernelExploit.asset" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Shop/Sizeof.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Shop/Sizeof.asset" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Assets/Data/Cards/Shop/Sizeof.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Data/Cards/Shop/Sizeof.asset" 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/Combat/CardEffects.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/CardEffects.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/Combat/CombatContext.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/CombatContext.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/CombatManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/CombatManager.cs" 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/Scripts/Combat/CombatTuning.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/CombatTuning.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/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/DamagePipeline.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/DamagePipeline.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/EnemyArchetype.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/EnemyArchetype.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/EnemyInstance.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/EnemyInstance.cs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/EnemyInstance.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/EnemyInstance.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/EnemyIntent.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/EnemyIntent.cs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/StatusEffectController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/StatusEffectController.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Combat/RandomRoll.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Combat/RandomRoll.cs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/Assets/Scripts/Run/RepositoryOffer.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Run/RepositoryOffer.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Meta/GachaPullContext.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Meta/GachaPullContext.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/RunContext.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Run/RunContext.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/Run/RunManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Run/RunManager.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/GamblingSceneController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/GamblingSceneController.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Assets/Scripts/UI/MainMenuController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/MainMenuController.cs" 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" />
@@ -66,15 +76,22 @@
} }
}</component> }</component>
<component name="HighlightingSettingsPerFile"> <component name="HighlightingSettingsPerFile">
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Arch/AurHelper.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Arch/ConsultWiki.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Arch/PacmanSyu.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Fedora/BorrowChecker.asset" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Fedora/BorrowChecker.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/Fedora/CargoBuildRelease.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Fedora/DnfUpdate.asset" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Fedora/DnfUpdate.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Malloc.asset" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Malloc.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Pointer.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/C/Printf.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/Java/JavaPublicStaticMain.asset" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/Java/JavaPublicStaticMain.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/Java/JavaSystemOutPrintln.asset" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/Java/JavaSystemOutPrintln.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/Languages/Rust/RsBorrow.asset" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/Rust/RsBorrow.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/Rust/RsUnwrap.asset" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Languages/Rust/RsUnwrap.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Shop/Free.asset" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Shop/KernelExploit.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/Cards/Ubuntu/UbuntuProTrial.asset" root0="FORCE_HIGHLIGHTING" /> <setting file="file://$PROJECT_DIR$/Assets/Data/Cards/Ubuntu/UbuntuProTrial.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/DistroDatabase.asset" root0="FORCE_HIGHLIGHTING" />
@@ -111,23 +128,23 @@
<component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" /> <component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" />
<component name="NextEditCompletionFeaturesState"> <component name="NextEditCompletionFeaturesState">
<decayedCancelled> <decayedCancelled>
<entry key="MS100" value="1.5438155496731856" /> <entry key="MS100" value="1.743262326028233" />
<entry key="MS500" value="5.416837026621939" /> <entry key="MS500" value="5.976929062573193" />
<entry key="S2" value="12.668957872278758" /> <entry key="S2" value="17.53689034207893" />
<entry key="S5" value="16.026673635995547" /> <entry key="S5" value="27.584806857875957" />
<entry key="S10" value="17.426595593285192" /> <entry key="S10" value="33.27162043754185" />
<entry key="S30" value="18.454820586422397" /> <entry key="S30" value="38.14780953249457" />
<entry key="S60" value="18.72472582023672" /> <entry key="S60" value="39.53571836868974" />
<entry key="M2" value="18.86168439667557" /> <entry key="M2" value="40.262726944904955" />
<entry key="M5" value="18.95087896044715" /> <entry key="M5" value="43.32220297215448" />
<entry key="M10" value="19.972234082604384" /> <entry key="M10" value="66.17309975101882" />
<entry key="M15" value="25.48708802307527" /> <entry key="M15" value="106.65472090214925" />
<entry key="M30" value="70.33485962789716" /> <entry key="M30" value="269.18080676227913" />
<entry key="H1" value="193.24800336339004" /> <entry key="H1" value="561.7788280748766" />
<entry key="H2" value="411.97552826367775" /> <entry key="H2" value="954.5740596560953" />
<entry key="H4" value="796.6715755808564" /> <entry key="H4" value="1474.025592246917" />
<entry key="D1" value="3240.2139167478695" /> <entry key="D1" value="4120.268616608263" />
<entry key="W1" value="5547.574956248048" /> <entry key="W1" value="6567.8208311010485" />
</decayedCancelled> </decayedCancelled>
<decayedSelected> <decayedSelected>
<entry key="MS100" value="0.0" /> <entry key="MS100" value="0.0" />
@@ -138,34 +155,34 @@
<entry key="S30" value="0.0" /> <entry key="S30" value="0.0" />
<entry key="S60" value="0.0" /> <entry key="S60" value="0.0" />
<entry key="M2" value="0.0" /> <entry key="M2" value="0.0" />
<entry key="M5" value="6.004482652781533E-147" /> <entry key="M5" value="9.303102041982209E-153" />
<entry key="M10" value="7.748859692098003E-74" /> <entry key="M10" value="9.645258960745102E-77" />
<entry key="M15" value="1.817573009109616E-49" /> <entry key="M15" value="2.1031775057960495E-51" />
<entry key="M30" value="4.263301749081961E-25" /> <entry key="M30" value="4.586042805192191E-26" />
<entry key="H1" value="6.535814477556539E-13" /> <entry key="H1" value="2.1436097902215403E-13" />
<entry key="H2" value="8.519293271851925E-7" /> <entry key="H2" value="4.878952351612686E-7" />
<entry key="H4" value="0.0012617458857475668" /> <entry key="H4" value="9.54846149789426E-4" />
<entry key="D1" value="0.9778028711909533" /> <entry key="D1" value="0.9334224145260533" />
<entry key="W1" value="3.2663828053013284" /> <entry key="W1" value="3.244779666296953" />
</decayedSelected> </decayedSelected>
<decayedShown> <decayedShown>
<entry key="MS100" value="1.4317187710347317" /> <entry key="MS100" value="1.668533264021644" />
<entry key="MS500" value="5.34064632290837" /> <entry key="MS500" value="5.913573457970713" />
<entry key="S2" value="12.621006367063478" /> <entry key="S2" value="17.47919105497625" />
<entry key="S5" value="16.001716646214877" /> <entry key="S5" value="27.548161746345162" />
<entry key="S10" value="17.412885614576528" /> <entry key="S10" value="33.249516115897215" />
<entry key="S30" value="18.449946114998543" /> <entry key="S30" value="38.1393616328719" />
<entry key="S60" value="18.722248425273893" /> <entry key="S60" value="39.53134062098556" />
<entry key="M2" value="18.860435494937665" /> <entry key="M2" value="40.26049784489663" />
<entry key="M5" value="18.95037608361192" /> <entry key="M5" value="43.32125926171084" />
<entry key="M10" value="19.971930307306003" /> <entry key="M10" value="66.17242872752867" />
<entry key="M15" value="25.486739213245762" /> <entry key="M15" value="106.65399822613271" />
<entry key="M30" value="70.33427160959226" /> <entry key="M30" value="269.1797553890588" />
<entry key="H1" value="193.24731132495674" /> <entry key="H1" value="561.7775677248208" />
<entry key="H2" value="411.97491708219036" /> <entry key="H2" value="954.5729275577665" />
<entry key="H4" value="796.6723332411082" /> <entry key="H4" value="1474.025680151079" />
<entry key="D1" value="3241.1913812104362" /> <entry key="D1" value="4121.2016186941155" />
<entry key="W1" value="5550.841244628519" /> <entry key="W1" value="6571.065502619166" />
</decayedShown> </decayedShown>
</component> </component>
<component name="ProjectColorInfo">{ <component name="ProjectColorInfo">{
@@ -258,7 +275,8 @@
<workItem from="1783507489679" duration="5909000" /> <workItem from="1783507489679" duration="5909000" />
<workItem from="1783519887449" duration="6425000" /> <workItem from="1783519887449" duration="6425000" />
<workItem from="1783578446731" duration="20118000" /> <workItem from="1783578446731" duration="20118000" />
<workItem from="1783615847433" duration="3713000" /> <workItem from="1783615847433" duration="3791000" />
<workItem from="1783665689762" duration="4791000" />
</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" />
@@ -484,7 +502,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1783614775509</updated> <updated>1783614775509</updated>
</task> </task>
<option name="localTasksCounter" value="29" /> <task id="LOCAL-00029" summary="Fix C, more enemies and less scaling for enemies.">
<option name="closed" value="true" />
<created>1783620160977</created>
<option name="number" value="00029" />
<option name="presentableId" value="LOCAL-00029" />
<option name="project" value="LOCAL" />
<updated>1783620160977</updated>
</task>
<option name="localTasksCounter" value="30" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -500,7 +526,6 @@
<component name="UnityUnitTestConfiguration" currentTestLauncher="Both" /> <component name="UnityUnitTestConfiguration" currentTestLauncher="Both" />
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" /> <option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
<MESSAGE value="Add actual ASCII-Art for distros, add first three distros and being able to choose a starting-distro. Update UI to fix multiple small bugs." />
<MESSAGE value="refactor card systems" /> <MESSAGE value="refactor card systems" />
<MESSAGE value="Add actual carts and shit&#10;&#10;Upgrade UI to Level 6&#10;&#10;Balls" /> <MESSAGE value="Add actual carts and shit&#10;&#10;Upgrade UI to Level 6&#10;&#10;Balls" />
<MESSAGE value="Implement more UX and cards for other distros" /> <MESSAGE value="Implement more UX and cards for other distros" />
@@ -525,7 +550,8 @@
<MESSAGE value="restructure packages (it looks shit)" /> <MESSAGE value="restructure packages (it looks shit)" />
<MESSAGE value="Make multiple UX improvements in distro -&gt; packages" /> <MESSAGE value="Make multiple UX improvements in distro -&gt; packages" />
<MESSAGE value="Implement Arch and some additional changes" /> <MESSAGE value="Implement Arch and some additional changes" />
<option name="LAST_COMMIT_MESSAGE" value="Implement Arch and some additional changes" /> <MESSAGE value="Fix C, more enemies and less scaling for enemies." />
<option name="LAST_COMMIT_MESSAGE" value="Fix C, more enemies and less scaling for enemies." />
</component> </component>
<component name="XDebuggerManager"> <component name="XDebuggerManager">
<breakpoint-manager> <breakpoint-manager>
+28
View File
@@ -0,0 +1,28 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
m_Name: AurHelper
m_EditorClassIdentifier:
id: arch_aur_helper
displayName: AUR helper
description: add a random common C or Rust card to hand. it costs 0 this turn.
flavorText: definitely read the PKGBUILD. definitely.
language: 2
rarity: 1
cycleCost: 1
resolutionTrack: 0
distroExclusive: 1
isToken: 0
isRunOnly: 0
designNotes: Arch exclusive. Uses existing generated-card pool; reports TODO if C/Rust commons are unavailable.
requiresSystem: []
upgradeTarget: {fileID: 0}
effects: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aa110000000000000000000000000004
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+30
View File
@@ -0,0 +1,30 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
m_Name: ConsultWiki
m_EditorClassIdentifier:
id: arch_consult_wiki
displayName: Consult the Wiki
description: fix a broken card and draw 1. if none are broken, deal 6.
flavorText: the answer was in a footnote from 2013.
language: 2
rarity: 0
cycleCost: 0
resolutionTrack: 0
distroExclusive: 1
isToken: 0
isRunOnly: 0
designNotes: Arch exclusive. Uses existing IsBroken flag if present; card-break creation is TODO, draw/damage fallback works now.
requiresSystem:
- card-break
upgradeTarget: {fileID: 0}
effects: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aa110000000000000000000000000002
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+28
View File
@@ -0,0 +1,28 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
m_Name: Makepkg
m_EditorClassIdentifier:
id: arch_makepkg
displayName: makepkg
description: deal 7 + btw stacks x3; x4 at Arch v3+. overkill spills. 20% segfault: uptime -3 and corrupt a random card in hand.
flavorText: it built on my machine, which is the only machine.
language: 0
rarity: 1
cycleCost: 2
resolutionTrack: 0
distroExclusive: 1
isToken: 0
isRunOnly: 0
designNotes: Arch exclusive. Reads CombatantState.ArchBtwStacks, then damage still routes through DamagePipeline and receives normal btw flat bonus too.
requiresSystem: []
upgradeTarget: {fileID: 0}
effects: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aa110000000000000000000000000003
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,28 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
m_Name: MinimalInstall
m_EditorClassIdentifier:
id: arch_minimal_install
displayName: minimal install
description: exhaust up to 2 cards from hand. per card exhausted, gain 3 Shield and 1 Cycle.
flavorText: no desktop, no browser, no regrets.
language: 2
rarity: 1
cycleCost: 1
resolutionTrack: 0
distroExclusive: 1
isToken: 0
isRunOnly: 0
designNotes: Arch exclusive. Exhausts first available hand cards using existing exhaust pile, shield, and cycle systems.
requiresSystem: []
upgradeTarget: {fileID: 0}
effects: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aa110000000000000000000000000005
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+29
View File
@@ -0,0 +1,29 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5178747fac4dbfc448ff15d8a687e03c, type: 3}
m_Name: PacmanSyu
m_EditorClassIdentifier:
id: arch_pacman_syu
displayName: pacman -Syu
description: deal 14. overkill spills. 20% segfault: uptime -3 and corrupt a random card in hand.
flavorText: full system upgrade. what could possibly ABI wrong?
language: 0
rarity: 1
cycleCost: 1
resolutionTrack: 0
distroExclusive: 1
isToken: 0
isRunOnly: 0
designNotes: Arch exclusive. Damage and segfault card corruption are live through DamagePipeline and the broken-card hand state.
requiresSystem: []
upgradeTarget: {fileID: 0}
effects: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aa110000000000000000000000000001
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+1 -1
View File
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
id: fedora_dnf_update id: fedora_dnf_update
displayName: dnf update displayName: dnf update
description: deal 9. costs 1 less per java card played this combat (min 0). description: deal 9. overkill spills. costs 1 less per java card played this combat (min 0).
flavorText: today everything gets newer. flavorText: today everything gets newer.
language: 8 language: 8
rarity: 0 rarity: 0
+1 -1
View File
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
id: lang_c_malloc id: lang_c_malloc
displayName: malloc() displayName: malloc()
description: deal 8. 20% segfault: uptime -3. description: deal 12. overkill spills. 20% segfault: uptime -3 and corrupt a random card in hand.
flavorText: memory acquired. ownership emotionally unresolved. flavorText: memory acquired. ownership emotionally unresolved.
language: 0 language: 0
rarity: 0 rarity: 0
+1 -1
View File
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
id: lang_c_pointer id: lang_c_pointer
displayName: pointer displayName: pointer
description: deal 6. if the previous card was C, deal 10 instead. 20% segfault: uptime -3. description: deal 9. if the previous card was C, deal 15 instead. overkill spills. 20% segfault: uptime -3 and corrupt a random card in hand.
flavorText: points at the bug with suspicious confidence. flavorText: points at the bug with suspicious confidence.
language: 0 language: 0
rarity: 0 rarity: 0
+1 -1
View File
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
id: lang_c_printf id: lang_c_printf
displayName: printf() displayName: printf()
description: deal 4. 20% segfault: uptime -3. description: deal 7. overkill spills. 20% segfault: uptime -3 and corrupt a random card in hand.
flavorText: debugging by yelling into the void. flavorText: debugging by yelling into the void.
language: 0 language: 0
rarity: 0 rarity: 0
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
id: lang_java_new_object id: lang_java_new_object
displayName: new Object() displayName: new Object()
description: uses 2 RAM. deal 3. JIT applies. description: uses 2 RAM. deal 3. overkill spills. JIT applies.
flavorText: allocation as a love language. flavorText: allocation as a love language.
language: 8 language: 8
rarity: 0 rarity: 0
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
id: lang_java_public_static_main id: lang_java_public_static_main
displayName: public static void main displayName: public static void main
description: uses 2 RAM. deal 7. JIT: Java cards cost 1 less this combat each time you play a Java card; the discount decays by 1 after each wave. description: uses 2 RAM. deal 7. overkill spills. JIT: Java cards cost 1 less this combat each time you play a Java card; the discount decays by 1 after each wave.
flavorText: verbose, but warmed up. flavorText: verbose, but warmed up.
language: 8 language: 8
rarity: 0 rarity: 0
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
id: lang_java_system_out_println id: lang_java_system_out_println
displayName: System.out.println displayName: System.out.println
description: uses 2 RAM. deal 5. JIT applies. description: uses 2 RAM. deal 5. overkill spills. JIT applies.
flavorText: standard output, standard damage. flavorText: standard output, standard damage.
language: 8 language: 8
rarity: 0 rarity: 0
+1 -1
View File
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
id: shop_c_free id: shop_c_free
displayName: free() displayName: free()
description: deal 12. 20% segfault: uptime -3 and draw 2. description: deal 18. overkill spills. 20% segfault: uptime -3, corrupt a random card in hand, and draw 2.
flavorText: double-free your schedule, not your heap. flavorText: double-free your schedule, not your heap.
language: 0 language: 0
rarity: 0 rarity: 0
+1 -1
View File
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
id: shop_c_kernel_exploit id: shop_c_kernel_exploit
displayName: kernel_exploit displayName: kernel_exploit
description: deal 25 ignoring Shield. 50% segfault: uptime -8. description: deal 36 ignoring Shield. overkill spills. 50% segfault: uptime -8 and corrupt a random card in hand.
flavorText: privilege escalated. heartbeat also escalated. flavorText: privilege escalated. heartbeat also escalated.
language: 0 language: 0
rarity: 1 rarity: 1
+1 -1
View File
@@ -14,7 +14,7 @@ MonoBehaviour:
m_EditorClassIdentifier: m_EditorClassIdentifier:
id: shop_c_sizeof id: shop_c_sizeof
displayName: sizeof() displayName: sizeof()
description: deal 3 x C cards in hand. 20% segfault: uptime -3. description: deal 5 x C cards in hand. overkill spills. 20% segfault: uptime -3 and corrupt a random card in hand.
flavorText: compile-time confidence, runtime consequences. flavorText: compile-time confidence, runtime consequences.
language: 0 language: 0
rarity: 1 rarity: 1
+71
View File
@@ -400,10 +400,23 @@
font-size: 10px; font-size: 10px;
} }
.enemy-scroll {
flex-grow: 1;
min-height: 0;
overflow: hidden;
}
.enemy-scroll .unity-scroll-view__content-container {
flex-direction: row;
flex-wrap: wrap;
align-content: flex-start;
}
.enemy-row { .enemy-row {
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
flex-grow: 1; flex-grow: 1;
min-height: 0;
align-content: flex-start; align-content: flex-start;
} }
@@ -422,6 +435,64 @@
color: var(--kp-text); color: var(--kp-text);
} }
.enemy-row-compact {
align-content: flex-start;
}
.enemy-card-compact {
width: 98px;
min-height: 126px;
margin-right: 6px;
margin-bottom: 6px;
padding: 6px;
}
.enemy-card-compact .enemy-name {
font-size: 12px;
margin-bottom: 5px;
}
.enemy-card-compact .meter-block {
margin-bottom: 6px;
}
.enemy-card-compact .meter-head {
margin-bottom: 3px;
}
.enemy-card-compact .metric-label,
.enemy-card-compact .status-label,
.enemy-card-compact .enemy-shield {
font-size: 10px;
}
.enemy-card-compact .metric-value,
.enemy-card-compact .metric-value-muted {
font-size: 11px;
}
.enemy-card-compact .meter-track {
height: 10px;
}
.enemy-card-compact .status-pip,
.enemy-card-compact .tag {
font-size: 9px;
padding-left: 4px;
padding-right: 4px;
margin-right: 3px;
margin-bottom: 3px;
}
.enemy-card-compact .intent-panel {
margin-top: 5px;
padding: 5px;
}
.enemy-card-compact .intent-value {
font-size: 15px;
}
.enemy-card:hover { .enemy-card:hover {
border-color: var(--kp-border-strong); border-color: var(--kp-border-strong);
} }
+89 -25
View File
@@ -7,6 +7,9 @@ namespace KernelPanic.Combat
{ {
public sealed class DealDamageEffect : ICardEffect public sealed class DealDamageEffect : ICardEffect
{ {
private const int RustOverflowRollMax = 1000000;
private const string RustUnsigned64LimitText = "18446744073709551615";
private readonly int _minAmount; private readonly int _minAmount;
private readonly int _maxAmount; private readonly int _maxAmount;
private readonly Language _language; private readonly Language _language;
@@ -49,22 +52,80 @@ namespace KernelPanic.Combat
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));
bool rustUnsignedOverflow = IsRustDamage(context)
&& RandomRoll.RollRange(1, RustOverflowRollMax, new RollContext(context.Source)) == 1;
if (rustUnsignedOverflow)
{
amount = int.MaxValue;
context.CombatManager.ReportEffectResult($"rust unsigned overflow: {RustUnsigned64LimitText}");
}
bool rustOverkillToShield = _language == Language.Rust || context.Card?.Definition?.Language == Language.Rust; bool rustOverkillToShield = IsRustDamage(context);
int survivable = rustOverkillToShield bool overkillSpills = IsSpillDamage(context);
? _trueDamage ? target.CurrentUptime : target.CurrentUptime + target.Shield int survivable = rustOverkillToShield || overkillSpills
? GetSurvivableDamage(target, _trueDamage)
: 0; : 0;
DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, _trueDamage, _canCrit)); DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, _trueDamage, _canCrit && !rustUnsignedOverflow));
dealtDamage |= result.IncomingAmount > 0; dealtDamage |= result.IncomingAmount > 0;
if (rustOverkillToShield) if (rustOverkillToShield)
{ {
GrantRustOverkillShield(context, result, survivable); GrantRustOverkillShield(context, result, survivable);
} }
if (overkillSpills)
{
dealtDamage |= SpillOverkillDamage(context, target, result, survivable);
}
} }
SegfaultRecoil.TryApply(context, _language, dealtDamage); SegfaultRecoil.TryApply(context, _language, dealtDamage);
} }
private bool IsRustDamage(CombatContext context)
{
return _language == Language.Rust || context.Card?.Definition?.Language == Language.Rust;
}
private bool IsSpillDamage(CombatContext context)
{
Language cardLanguage = context.Card?.Definition == null ? _language : context.Card.Definition.Language;
return _language == Language.C
|| _language == Language.Java
|| cardLanguage == Language.C
|| cardLanguage == Language.Java;
}
private static int GetSurvivableDamage(CombatantState target, bool trueDamage)
{
return target == null ? 0 : trueDamage ? target.CurrentUptime : target.CurrentUptime + target.Shield;
}
private bool SpillOverkillDamage(CombatContext context, CombatantState firstTarget, DamageResult firstResult, int firstSurvivable)
{
int remaining = UnityEngine.Mathf.Max(0, firstResult.IncomingAmount - firstSurvivable);
if (remaining <= 0)
{
return false;
}
bool dealtDamage = false;
for (int i = 0; i < context.Enemies.Count && remaining > 0; i++)
{
CombatantState spillTarget = context.Enemies[i].State;
if (spillTarget == null || spillTarget == firstTarget || spillTarget.IsDefeated)
{
continue;
}
int survivable = GetSurvivableDamage(spillTarget, _trueDamage);
DamageResult spillResult = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, spillTarget, remaining, _language, _trueDamage, false, applySourceModifiers: false));
dealtDamage |= spillResult.IncomingAmount > 0;
remaining = UnityEngine.Mathf.Max(0, spillResult.IncomingAmount - survivable);
}
return dealtDamage;
}
private static void GrantRustOverkillShield(CombatContext context, DamageResult result, int survivable) private static void GrantRustOverkillShield(CombatContext context, DamageResult result, int survivable)
{ {
int overkill = UnityEngine.Mathf.Max(0, result.IncomingAmount - survivable); int overkill = UnityEngine.Mathf.Max(0, result.IncomingAmount - survivable);
@@ -165,7 +226,9 @@ namespace KernelPanic.Combat
continue; continue;
} }
int roll = RandomRoll.RollRange(1, 100, new RollContext(context.Source)); int roll = context.Source != null && context.Source.ForceMaxRolls
? 1
: RandomRoll.RollRange(1, 100, RollContext.None);
int damageAmount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_damageAmount, context), context.Card); int damageAmount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_damageAmount, context), context.Card);
int amount = roll <= _successPercent ? damageAmount : 0; int amount = roll <= _successPercent ? damageAmount : 0;
DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, false, true)); DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, false, true));
@@ -308,7 +371,7 @@ namespace KernelPanic.Combat
public void Execute(CombatContext context) public void Execute(CombatContext context)
{ {
int cCards = context.HandController.Cards.Count(card => card?.Definition != null && card.Definition.Language == Language.C); int cCards = context.HandController.Cards.Count(card => card?.Definition != null && card.Definition.Language == Language.C);
int amount = 3 * cCards; int amount = 5 * cCards;
new DealDamageEffect(amount, amount, Language.C).Execute(context); new DealDamageEffect(amount, amount, Language.C).Execute(context);
} }
} }
@@ -873,6 +936,7 @@ namespace KernelPanic.Combat
DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(null, context.Source, uptimeDamage, Language.C, true, false)); DamageResult result = context.DamagePipeline.DealDamage(new DamageRequest(null, context.Source, uptimeDamage, Language.C, true, false));
context.CombatManager.ReportEffectResult($"segfault: uptime -{result.FinalAmount}"); context.CombatManager.ReportEffectResult($"segfault: uptime -{result.FinalAmount}");
context.CombatManager.TryCorruptRandomHandCard("segfault");
if (drawOnSegfault) if (drawOnSegfault)
{ {
context.CombatManager.DrawCards(2, "free() recoil"); context.CombatManager.DrawCards(2, "free() recoil");
@@ -922,9 +986,9 @@ namespace KernelPanic.Combat
new DrawEffect(1) new DrawEffect(1)
}, },
"lang_rs_borrow" => One(new ShieldEffect(5)), "lang_rs_borrow" => One(new ShieldEffect(5)),
"lang_c_malloc" => One(new DealDamageEffect(8, 8, Language.C)), "lang_c_malloc" => One(new DealDamageEffect(12, 12, Language.C)),
"lang_c_printf" => One(new DealDamageEffect(4, 4, Language.C)), "lang_c_printf" => One(new DealDamageEffect(7, 7, Language.C)),
"lang_c_pointer" => One(new PreviousLanguageDamageEffect(6, 10, Language.C)), "lang_c_pointer" => One(new PreviousLanguageDamageEffect(9, 15, Language.C)),
"lang_java_public_static_main" => One(new DealDamageEffect(7, 7, Language.Java)), "lang_java_public_static_main" => One(new DealDamageEffect(7, 7, Language.Java)),
"lang_java_system_out_println" => One(new DealDamageEffect(5, 5, Language.Java)), "lang_java_system_out_println" => One(new DealDamageEffect(5, 5, Language.Java)),
"lang_java_new_object" => One(new DealDamageEffect(3, 3, Language.Java)), "lang_java_new_object" => One(new DealDamageEffect(3, 3, Language.Java)),
@@ -976,14 +1040,14 @@ namespace KernelPanic.Combat
"fedora_dnf_update" => One(new DealDamageEffect(9, 9, Language.Java)), "fedora_dnf_update" => One(new DealDamageEffect(9, 9, Language.Java)),
"fedora_rawhide" => One(new RawhideEffect()), "fedora_rawhide" => One(new RawhideEffect()),
"fedora_selinux" => One(new IncomingAttackHalveEffect(2)), "fedora_selinux" => One(new IncomingAttackHalveEffect(2)),
"arch_pacman_syu" => One(new DealDamageEffect(8, 8, Language.C)), "arch_pacman_syu" => One(new DealDamageEffect(14, 14, Language.C)),
"arch_consult_wiki" => One(new ConsultWikiEffect()), "arch_consult_wiki" => One(new ConsultWikiEffect()),
"arch_makepkg" => One(new BtwScaledDamageEffect(4, 2, Language.C)), "arch_makepkg" => One(new BtwScaledDamageEffect(7, 3, Language.C)),
"arch_aur_helper" => One(new RandomCommonLanguageCardEffect()), "arch_aur_helper" => One(new RandomCommonLanguageCardEffect()),
"arch_minimal_install" => One(new MinimalInstallEffect()), "arch_minimal_install" => One(new MinimalInstallEffect()),
"shop_c_free" => One(new DealDamageEffect(12, 12, Language.C)), "shop_c_free" => One(new DealDamageEffect(18, 18, Language.C)),
"shop_c_sizeof" => One(new SizeofDamageEffect()), "shop_c_sizeof" => One(new SizeofDamageEffect()),
"shop_c_kernel_exploit" => One(new DealDamageEffect(25, 25, Language.C, true)), "shop_c_kernel_exploit" => One(new DealDamageEffect(36, 36, Language.C, true)),
_ => Todo("card effect is not authored") _ => Todo("card effect is not authored")
}; };
} }
@@ -1053,12 +1117,12 @@ namespace KernelPanic.Combat
"lang_rs_fn_main" => $"deal {UpgradeMath.ScaleAmount(6, card)}. overkill becomes shield.{marker}", "lang_rs_fn_main" => $"deal {UpgradeMath.ScaleAmount(6, card)}. overkill becomes shield.{marker}",
"lang_rs_unwrap" => $"deal {UpgradeMath.ScaleAmount(9, card)}. overkill becomes shield. draw {UpgradeMath.ScaleAmount(1, card)}.{marker}", "lang_rs_unwrap" => $"deal {UpgradeMath.ScaleAmount(9, card)}. overkill becomes shield. draw {UpgradeMath.ScaleAmount(1, card)}.{marker}",
"lang_rs_borrow" => $"gain {UpgradeMath.ScaleShield(5, card)} shield.{marker}", "lang_rs_borrow" => $"gain {UpgradeMath.ScaleShield(5, card)} shield.{marker}",
"lang_c_malloc" => $"deal {UpgradeMath.ScaleAmount(8, card)}. 20%: segfault, uptime -3.{marker}", "lang_c_malloc" => $"deal {UpgradeMath.ScaleAmount(12, card)}. overkill spills. 20%: segfault, uptime -3 and corrupt a hand card.{marker}",
"lang_c_printf" => $"deal {UpgradeMath.ScaleAmount(4, card)}. 20%: segfault, uptime -3.{marker}", "lang_c_printf" => $"deal {UpgradeMath.ScaleAmount(7, card)}. overkill spills. 20%: segfault, uptime -3 and corrupt a hand card.{marker}",
"lang_c_pointer" => $"deal {UpgradeMath.ScaleAmount(6, card)}; {UpgradeMath.ScaleAmount(10, card)} if the previous card was C. 20%: segfault, uptime -3.{marker}", "lang_c_pointer" => $"deal {UpgradeMath.ScaleAmount(9, card)}; {UpgradeMath.ScaleAmount(15, card)} if the previous card was C. overkill spills. 20%: segfault, uptime -3 and corrupt a hand card.{marker}",
"lang_java_public_static_main" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(7, card)}. JIT: Java cards cost 1 less this combat each time you play a Java card; discount decays by 1 after each wave.{marker}", "lang_java_public_static_main" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(7, card)}. overkill spills. JIT: Java cards cost 1 less this combat each time you play a Java card; discount decays by 1 after each wave.{marker}",
"lang_java_system_out_println" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(5, card)}. JIT applies; discount decays by 1 after each wave.{marker}", "lang_java_system_out_println" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(5, card)}. overkill spills. JIT applies; discount decays by 1 after each wave.{marker}",
"lang_java_new_object" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(3, card)}. JIT applies; discount decays by 1 after each wave.{marker}", "lang_java_new_object" => $"uses 2 RAM. deal {UpgradeMath.ScaleAmount(3, card)}. overkill spills. JIT applies; discount decays by 1 after each wave.{marker}",
"shop_py_list_append" => $"queue: deal {UpgradeMath.ScaleAmount(4 + UnityEngine.Mathf.Max(0, card.QueuePlayCount - 1), card)}; +1 each time re-queued.{marker}", "shop_py_list_append" => $"queue: deal {UpgradeMath.ScaleAmount(4 + UnityEngine.Mathf.Max(0, card.QueuePlayCount - 1), card)}; +1 each time re-queued.{marker}",
"shop_py_async_def" => $"queue: draw {UpgradeMath.ScaleAmount(2, card)}. gain {UpgradeMath.ScaleAmount(1, card)} cycle next turn.{marker}", "shop_py_async_def" => $"queue: draw {UpgradeMath.ScaleAmount(2, card)}. gain {UpgradeMath.ScaleAmount(1, card)} cycle next turn.{marker}",
"shop_py_zip" => $"next {UpgradeMath.ScaleAmount(2, card)} queued cards resolve twice.{marker}", "shop_py_zip" => $"next {UpgradeMath.ScaleAmount(2, card)} queued cards resolve twice.{marker}",
@@ -1077,17 +1141,17 @@ namespace KernelPanic.Combat
"mint_timeshift" => $"queue: record uptime now. at end of your next turn, restore to it if lower.{marker}", "mint_timeshift" => $"queue: record uptime now. at end of your next turn, restore to it if lower.{marker}",
"fedora_dnf_autoremove" => $"deal {UpgradeMath.ScaleAmount(7, card)}. exhaust a junk/NOP/broken card from hand; if you do, gain {UpgradeMath.ScaleAmount(1, card)} cycle. if not, do nothing.{marker}", "fedora_dnf_autoremove" => $"deal {UpgradeMath.ScaleAmount(7, card)}. exhaust a junk/NOP/broken card from hand; if you do, gain {UpgradeMath.ScaleAmount(1, card)} cycle. if not, do nothing.{marker}",
"fedora_first_to_package" => $"deal {UpgradeMath.ScaleAmount(7, card)}. Java cards cost 1 less this turn.{marker}", "fedora_first_to_package" => $"deal {UpgradeMath.ScaleAmount(7, card)}. Java cards cost 1 less this turn.{marker}",
"fedora_dnf_update" => $"deal {UpgradeMath.ScaleAmount(9, card)}. costs 1 less per Java card played this combat.{marker}", "fedora_dnf_update" => $"deal {UpgradeMath.ScaleAmount(9, card)}. overkill spills. costs 1 less per Java card played this combat.{marker}",
"fedora_rawhide" => $"draw {UpgradeMath.ScaleAmount(2, card)}. next card this turn gains bleeding edge.{marker}", "fedora_rawhide" => $"draw {UpgradeMath.ScaleAmount(2, card)}. next card this turn gains bleeding edge.{marker}",
"fedora_selinux" => $"next {UpgradeMath.ScaleAmount(2, card)} enemy attacks deal half damage.{marker}", "fedora_selinux" => $"next {UpgradeMath.ScaleAmount(2, card)} enemy attacks deal half damage.{marker}",
"arch_pacman_syu" => $"deal {UpgradeMath.ScaleAmount(8, card)}. 20%: segfault, uptime -3. TODO: 20% break a random hand card.{marker}", "arch_pacman_syu" => $"deal {UpgradeMath.ScaleAmount(14, card)}. overkill spills. 20%: segfault, uptime -3 and corrupt a hand card.{marker}",
"arch_consult_wiki" => $"fix a broken card and draw {UpgradeMath.ScaleAmount(1, card)}; if none, deal {UpgradeMath.ScaleAmount(6, card)}.{marker}", "arch_consult_wiki" => $"fix a broken card and draw {UpgradeMath.ScaleAmount(1, card)}; if none, deal {UpgradeMath.ScaleAmount(6, card)}.{marker}",
"arch_makepkg" => $"deal {UpgradeMath.ScaleAmount(4, card)} + btw x2; x3 at Arch v3+. 20%: segfault, uptime -3.{marker}", "arch_makepkg" => $"deal {UpgradeMath.ScaleAmount(7, card)} + btw x3; x4 at Arch v3+. overkill spills. 20%: segfault, uptime -3 and corrupt a hand card.{marker}",
"arch_aur_helper" => $"add a random C/Rust common card to hand; it costs 0 this turn.{marker}", "arch_aur_helper" => $"add a random C/Rust common card to hand; it costs 0 this turn.{marker}",
"arch_minimal_install" => $"exhaust up to 2 hand cards. per card: gain 3 Shield and 1 Cycle.{marker}", "arch_minimal_install" => $"exhaust up to 2 hand cards. per card: gain 3 Shield and 1 Cycle.{marker}",
"shop_c_free" => $"deal {UpgradeMath.ScaleAmount(12, card)}. 20%: segfault, uptime -3 and draw 2.{marker}", "shop_c_free" => $"deal {UpgradeMath.ScaleAmount(18, card)}. overkill spills. 20%: segfault, uptime -3, corrupt a hand card, and draw 2.{marker}",
"shop_c_sizeof" => $"deal 3 x C cards in hand. 20%: segfault, uptime -3.{marker}", "shop_c_sizeof" => $"deal 5 x C cards in hand. overkill spills. 20%: segfault, uptime -3 and corrupt a hand card.{marker}",
"shop_c_kernel_exploit" => $"deal {UpgradeMath.ScaleAmount(25, card)} ignoring Shield. 50%: segfault, uptime -8.{marker}", "shop_c_kernel_exploit" => $"deal {UpgradeMath.ScaleAmount(36, card)} ignoring Shield. overkill spills. 50%: segfault, uptime -8 and corrupt a hand card.{marker}",
_ => string.IsNullOrWhiteSpace(card.Definition.Description) ? "effect not authored." : $"{card.Definition.Description}{marker}" _ => string.IsNullOrWhiteSpace(card.Definition.Description) ? "effect not authored." : $"{card.Definition.Description}{marker}"
}; };
} }
+311 -15
View File
@@ -53,6 +53,7 @@ namespace KernelPanic.Combat
private int _rawhideBonusCharges; private int _rawhideBonusCharges;
private int _queuedRepeatCharges; private int _queuedRepeatCharges;
private int _nextTurnCycleBonus; private int _nextTurnCycleBonus;
private int _nextRacePairId = 1;
private Coroutine _phaseCoroutine; private Coroutine _phaseCoroutine;
public TurnPhase CurrentPhase => currentPhase; public TurnPhase CurrentPhase => currentPhase;
@@ -77,6 +78,7 @@ namespace KernelPanic.Combat
private void Awake() private void Awake()
{ {
_runManager = GetComponent<RunManager>(); _runManager = GetComponent<RunManager>();
_damagePipeline.ResistanceResolver = ResolveDamageResistance;
} }
private void OnEnable() private void OnEnable()
@@ -184,6 +186,18 @@ namespace KernelPanic.Combat
return false; return false;
} }
if (card.IsBroken)
{
Log($"{GetCardName(card)} is corrupted");
return false;
}
if (card.IsLocked)
{
Log($"{GetCardName(card)} is locked");
return false;
}
bool fedoraBonus = CanApplyFedoraBonus(); bool fedoraBonus = CanApplyFedoraBonus();
int cost = GetEffectiveCardCost(card); int cost = GetEffectiveCardCost(card);
if (fedoraBonus) if (fedoraBonus)
@@ -343,6 +357,21 @@ namespace KernelPanic.Combat
} }
} }
public bool TryCorruptRandomHandCard(string sourceLabel)
{
CardInstance card = RandomHandCard(cardInstance => cardInstance != null && !cardInstance.IsBroken);
if (card == null)
{
Log($"{sourceLabel}: no card available to corrupt");
return false;
}
card.IsBroken = true;
Log($"{sourceLabel}: corrupted {GetCardName(card)}");
StateChanged?.Invoke();
return true;
}
public void ReportEffectResult(string message) public void ReportEffectResult(string message)
{ {
if (!string.IsNullOrWhiteSpace(message)) if (!string.IsNullOrWhiteSpace(message))
@@ -651,6 +680,7 @@ namespace KernelPanic.Combat
_pendingTargetCard = null; _pendingTargetCard = null;
_cardsPlayedThisWave = 0; _cardsPlayedThisWave = 0;
_turnNumberThisWave = 0; _turnNumberThisWave = 0;
_nextRacePairId = 1;
_packageShieldBonusTriggeredThisTurn = false; _packageShieldBonusTriggeredThisTurn = false;
_packageTimeshiftTriggeredThisWave = false; _packageTimeshiftTriggeredThisWave = false;
ResetArchWaveState(); ResetArchWaveState();
@@ -753,10 +783,10 @@ namespace KernelPanic.Combat
private IEnumerator ProcessEnemiesSequenced() private IEnumerator ProcessEnemiesSequenced()
{ {
int enemyCountAtPhaseStart = _enemies.Count; List<EnemyInstance> actors = BuildEnemyActionOrder();
for (int i = 0; i < enemyCountAtPhaseStart && i < _enemies.Count; i++) for (int i = 0; i < actors.Count; i++)
{ {
EnemyInstance enemy = _enemies[i]; EnemyInstance enemy = actors[i];
if (enemy.State.IsDefeated) if (enemy.State.IsDefeated)
{ {
continue; continue;
@@ -779,11 +809,17 @@ namespace KernelPanic.Combat
StateChanged?.Invoke(); StateChanged?.Invoke();
yield return Wait(CombatTuning.EnemyTelegraphDelaySeconds); yield return Wait(CombatTuning.EnemyTelegraphDelaySeconds);
ExecuteEnemyIntent(enemy, i); ExecuteEnemyIntent(enemy, _enemies.IndexOf(enemy));
ResolvePassiveEnemyAfterAction(enemy);
if (!enemy.State.IsDefeated)
{
enemy.MarkTurnSurvived(); enemy.MarkTurnSurvived();
}
GameEvents.RaiseEnemyActed(new EnemyActedEvent(enemy, enemy.CurrentIntent)); GameEvents.RaiseEnemyActed(new EnemyActedEvent(enemy, enemy.CurrentIntent));
RemoveDefeatedEnemies();
StateChanged?.Invoke(); StateChanged?.Invoke();
if (CheckLoss()) if (CheckWinOrLoss())
{ {
yield break; yield break;
} }
@@ -1519,7 +1555,7 @@ namespace KernelPanic.Combat
state.DamageMultiplierPercent = 100; state.DamageMultiplierPercent = 100;
state.CurrentCardDamageMultiplierPercent = 100; state.CurrentCardDamageMultiplierPercent = 100;
state.ArchBtwDamagePerStack = IsDistro("arch") && _runConfig.DistroVersion >= 2 ? 2 : 1; state.ArchBtwDamagePerStack = IsDistro("arch") && _runConfig.DistroVersion >= 2 ? 2 : 1;
state.ArchMakepkgBtwMultiplier = IsDistro("arch") && _runConfig.DistroVersion >= 3 ? 3 : 2; state.ArchMakepkgBtwMultiplier = IsDistro("arch") && _runConfig.DistroVersion >= 3 ? 4 : 3;
state.ArchRollingReleaseShieldOnSave = IsDistro("arch") && _runConfig.DistroVersion >= 4 ? 15 : 0; state.ArchRollingReleaseShieldOnSave = IsDistro("arch") && _runConfig.DistroVersion >= 4 ? 15 : 0;
state.ArchRollingReleaseCyclesOnSave = IsDistro("arch") && _runConfig.DistroVersion >= 4 ? 2 : 0; state.ArchRollingReleaseCyclesOnSave = IsDistro("arch") && _runConfig.DistroVersion >= 4 ? 2 : 0;
} }
@@ -1693,13 +1729,15 @@ namespace KernelPanic.Combat
{ {
if (_enemies[i].State.IsDefeated) if (_enemies[i].State.IsDefeated)
{ {
if (_enemies[i].PendingRevive) EnemyInstance enemy = _enemies[i];
if (enemy.PendingRevive)
{ {
continue; continue;
} }
Log($"{_enemies[i].Name} exited"); Log($"{enemy.Name} exited");
_runManager?.AddBits(CombatTuning.BitsPerKill); _runManager?.AddBits(CombatTuning.BitsPerKill);
HandleEnemyDeath(enemy);
_enemies.RemoveAt(i); _enemies.RemoveAt(i);
} }
} }
@@ -1710,23 +1748,135 @@ namespace KernelPanic.Combat
} }
} }
private void HandleEnemyDeath(EnemyInstance enemy)
{
if (enemy == null)
{
return;
}
if (enemy.HasBehavior(EnemyBehaviorFlags.SegfaultOnDeath))
{
ApplySegfaultDeathPayload(enemy);
}
if (enemy.HasBehavior(EnemyBehaviorFlags.RacePair))
{
BreakRacePair(enemy);
}
if (enemy.HasBehavior(EnemyBehaviorFlags.LeavesOrphan))
{
SpawnOrphanFromDeath(enemy);
}
}
private void ApplySegfaultDeathPayload(EnemyInstance enemy)
{
_statusEffects.Apply(_playerState, StatusType.Segfault, 1, -1, enemy.State, skipNextTick: true);
TryCorruptRandomHandCard("segfault");
}
private CardInstance RandomHandCard(Predicate<CardInstance> predicate)
{
if (_handController == null || _handController.Cards.Count == 0)
{
return null;
}
List<CardInstance> candidates = new();
for (int i = 0; i < _handController.Cards.Count; i++)
{
CardInstance candidate = _handController.Cards[i];
if (predicate == null || predicate(candidate))
{
candidates.Add(candidate);
}
}
if (candidates.Count == 0)
{
return null;
}
int index = RandomRoll.RollRange(0, candidates.Count - 1, new RollContext(_playerState));
return candidates[index];
}
private void BreakRacePair(EnemyInstance defeated)
{
EnemyInstance survivor = RacePartner(defeated);
if (survivor == null)
{
return;
}
survivor.EnrageRaceSurvivor();
Log($"{survivor.Name} race link broke; survivor enraged");
}
private void SpawnOrphanFromDeath(EnemyInstance source)
{
EnemyInstance orphan = SpawnEnemy("orphan_process", 0);
orphan.MarkSpawnedFromDeath();
Log($"{source.Name} left an orphan process");
}
private void SpawnStructuralEnemies() private void SpawnStructuralEnemies()
{ {
int wave = CurrentWaveNumber; int wave = CurrentWaveNumber;
int countGrowth = ((wave - 1) / 2) * CombatTuning.AdditionalEnemiesPerWave; int countGrowth = ((wave - 1) / 2) * CombatTuning.AdditionalEnemiesPerWave;
int count = Mathf.Clamp(CombatTuning.BaseEnemiesPerWave + countGrowth, CombatTuning.BaseEnemiesPerWave, CombatTuning.MaxEnemiesPerWave); int count = Mathf.Clamp(CombatTuning.BaseEnemiesPerWave + countGrowth, CombatTuning.BaseEnemiesPerWave, CombatTuning.MaxEnemiesPerWave);
int waveUptimeBonus = Mathf.Max(0, wave - 1) * CombatTuning.EnemyUptimeGrowthPerWave; int waveUptimeBonus = Mathf.Max(0, wave - 1) * CombatTuning.EnemyUptimeGrowthPerWave;
bool rootkitSpawned = false;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
SpawnEnemy(CreatePlaceholderArchetypeId(wave, i), waveUptimeBonus); string archetypeId = CreatePlaceholderArchetypeId(wave, i);
if (archetypeId == "rootkit")
{
if (rootkitSpawned)
{
archetypeId = CreateRootkitFallbackArchetypeId(wave, i);
}
else
{
rootkitSpawned = true;
} }
} }
private void SpawnEnemy(string archetypeId, int uptimeBonus) if (archetypeId == "race_condition" && i + 1 < count)
{
SpawnRacePair(waveUptimeBonus);
i++;
continue;
}
if (archetypeId == "race_condition")
{
archetypeId = "segfault";
}
SpawnEnemy(archetypeId, waveUptimeBonus);
}
}
private void SpawnRacePair(int uptimeBonus)
{
int pairId = _nextRacePairId++;
EnemyInstance first = SpawnEnemy("race_condition", uptimeBonus);
EnemyInstance second = SpawnEnemy("race_condition", uptimeBonus);
first.SetPairId(pairId);
second.SetPairId(pairId);
Log("race condition pair spawned");
}
private EnemyInstance SpawnEnemy(string archetypeId, int uptimeBonus)
{ {
EnemyArchetypeDescriptor archetype = EnemyArchetypeCatalog.Get(archetypeId); EnemyArchetypeDescriptor archetype = EnemyArchetypeCatalog.Get(archetypeId);
int baseUptime = RandomRoll.RollRange(archetype.BaseUptimeMin, archetype.BaseUptimeMax, RollContext.None); int baseUptime = RandomRoll.RollRange(archetype.BaseUptimeMin, archetype.BaseUptimeMax, RollContext.None);
_enemies.Add(new EnemyInstance(archetype, baseUptime + Mathf.Max(0, uptimeBonus))); EnemyInstance enemy = new(archetype, baseUptime + Mathf.Max(0, uptimeBonus));
_enemies.Add(enemy);
return enemy;
} }
private static string CreatePlaceholderArchetypeId(int wave, int slotIndex) private static string CreatePlaceholderArchetypeId(int wave, int slotIndex)
@@ -1736,17 +1886,28 @@ namespace KernelPanic.Combat
{ {
<= 1 => new[] { "zombie_process", "memory_leak" }, <= 1 => new[] { "zombie_process", "memory_leak" },
2 => new[] { "zombie_process", "memory_leak", "fork_bomb", "firewalld" }, 2 => new[] { "zombie_process", "memory_leak", "fork_bomb", "firewalld" },
3 => new[] { "zombie_process", "memory_leak", "fork_bomb", "firewalld", "daemon" }, 3 => new[] { "zombie_process", "memory_leak", "fork_bomb", "firewalld", "segfault" },
_ => new[] { "zombie_process", "memory_leak", "fork_bomb", "firewalld", "daemon", "cron_job" } 4 => new[] { "zombie_process", "memory_leak", "fork_bomb", "firewalld", "cron_job", "segfault", "race_condition" },
_ => new[] { "zombie_process", "memory_leak", "fork_bomb", "firewalld", "daemon", "cron_job", "segfault", "race_condition", "rootkit" }
}; };
if (slotIndex == 0) if (slotIndex == 0)
{ {
if (wave >= 5 && wave % 2 == 1)
{
return "rootkit";
}
return wave % 2 == 0 ? "firewalld" : "zombie_process"; return wave % 2 == 0 ? "firewalld" : "zombie_process";
} }
if (slotIndex == 1 && wave >= 2) if (slotIndex == 1 && wave >= 2)
{ {
if (wave >= 4 && wave % 4 == 0)
{
return "race_condition";
}
return wave % 3 == 0 ? "fork_bomb" : "memory_leak"; return wave % 3 == 0 ? "fork_bomb" : "memory_leak";
} }
@@ -1754,6 +1915,13 @@ namespace KernelPanic.Combat
return pool[index]; return pool[index];
} }
private static string CreateRootkitFallbackArchetypeId(int wave, int slotIndex)
{
return slotIndex % 2 == 0
? "firewalld"
: wave % 3 == 0 ? "fork_bomb" : "memory_leak";
}
private void PickEnemyIntents() private void PickEnemyIntents()
{ {
for (int i = 0; i < _enemies.Count; i++) for (int i = 0; i < _enemies.Count; i++)
@@ -1765,6 +1933,37 @@ namespace KernelPanic.Combat
} }
} }
private List<EnemyInstance> BuildEnemyActionOrder()
{
List<EnemyInstance> actors = _enemies
.Where(enemy => enemy.State != null && !enemy.State.IsDefeated)
.ToList();
for (int i = 0; i < actors.Count - 1; i++)
{
EnemyInstance enemy = actors[i];
if (!IsRacePairLinked(enemy))
{
continue;
}
EnemyInstance partner = RacePartner(enemy);
int partnerIndex = partner == null ? -1 : actors.IndexOf(partner);
if (partnerIndex <= i)
{
continue;
}
if (RandomRoll.RollRange(0, 1, new RollContext(enemy.State)) == 1)
{
actors[i] = partner;
actors[partnerIndex] = enemy;
}
}
return actors;
}
private void ExecuteEnemyIntent(EnemyInstance enemy, int enemyIndex) private void ExecuteEnemyIntent(EnemyInstance enemy, int enemyIndex)
{ {
EnemyIntent intent = enemy.CurrentIntent; EnemyIntent intent = enemy.CurrentIntent;
@@ -1775,6 +1974,7 @@ namespace KernelPanic.Combat
switch (intent.Kind) switch (intent.Kind)
{ {
case EnemyIntentKind.Attack: case EnemyIntentKind.Attack:
value = ApplyEnemyAttackBonuses(enemy, value);
value = ApplyIncomingAttackMitigation(value); value = ApplyIncomingAttackMitigation(value);
DamageResult attackResult = _damagePipeline.DealDamage(new DamageRequest(enemy.State, _playerState, value, intent.DamageType, intent.TrueDamage, false)); DamageResult attackResult = _damagePipeline.DealDamage(new DamageRequest(enemy.State, _playerState, value, intent.DamageType, intent.TrueDamage, false));
GameEvents.RaisePlayerDamaged(new PlayerDamagedEvent(enemy, attackResult.FinalAmount)); GameEvents.RaisePlayerDamaged(new PlayerDamagedEvent(enemy, attackResult.FinalAmount));
@@ -1790,6 +1990,7 @@ namespace KernelPanic.Combat
} }
break; break;
case EnemyIntentKind.StatusAttack: case EnemyIntentKind.StatusAttack:
value = ApplyEnemyAttackBonuses(enemy, value);
DamageResult statusResult = _damagePipeline.DealDamage(new DamageRequest(enemy.State, _playerState, value, intent.DamageType, intent.TrueDamage, false)); DamageResult statusResult = _damagePipeline.DealDamage(new DamageRequest(enemy.State, _playerState, value, intent.DamageType, intent.TrueDamage, false));
GameEvents.RaisePlayerDamaged(new PlayerDamagedEvent(enemy, statusResult.FinalAmount)); GameEvents.RaisePlayerDamaged(new PlayerDamagedEvent(enemy, statusResult.FinalAmount));
if (intent.StatusStacks > 0) if (intent.StatusStacks > 0)
@@ -1810,15 +2011,50 @@ namespace KernelPanic.Combat
private void ExecuteSpecialEnemyIntent(EnemyInstance enemy) private void ExecuteSpecialEnemyIntent(EnemyInstance enemy)
{ {
if (enemy.HasBehavior(EnemyBehaviorFlags.Split)) if (enemy.HasBehavior(EnemyBehaviorFlags.SegfaultOnDeath))
{ {
TrySplitForkBomb(enemy); enemy.AdvanceSegfaultCountdown();
if (enemy.CountdownRemaining <= 0)
{
_damagePipeline.DealDamage(new DamageRequest(enemy.State, enemy.State, enemy.CurrentUptime, Language.C, true, false));
Log($"{enemy.Name} dereferenced null");
}
return; return;
} }
Log("TODO: special intent needs enemy-specific behavior hooks."); Log("TODO: special intent needs enemy-specific behavior hooks.");
} }
private void ResolvePassiveEnemyAfterAction(EnemyInstance enemy)
{
if (enemy == null || enemy.State.IsDefeated)
{
return;
}
if (enemy.HasBehavior(EnemyBehaviorFlags.Split))
{
TrySplitForkBomb(enemy);
}
}
private int ApplyEnemyAttackBonuses(EnemyInstance enemy, int value)
{
int modified = value;
if (IsRacePairLinked(enemy))
{
modified += EnemyArchetypeCatalog.RacePairDamageBonus;
}
if (enemy != null && enemy.RaceEnrageStacks > 0)
{
modified += enemy.RaceEnrageStacks;
}
return modified;
}
private void TrySplitForkBomb(EnemyInstance source) private void TrySplitForkBomb(EnemyInstance source)
{ {
int totalForkBombs = _enemies.Count(enemy => enemy.ArchetypeId == source.ArchetypeId && !enemy.State.IsDefeated); int totalForkBombs = _enemies.Count(enemy => enemy.ArchetypeId == source.ArchetypeId && !enemy.State.IsDefeated);
@@ -1836,6 +2072,34 @@ namespace KernelPanic.Combat
Log("fork bomb split"); Log("fork bomb split");
} }
private bool IsRacePairLinked(EnemyInstance enemy)
{
return enemy != null
&& enemy.HasBehavior(EnemyBehaviorFlags.RacePair)
&& RacePartner(enemy) != null;
}
private EnemyInstance RacePartner(EnemyInstance enemy)
{
if (enemy == null || enemy.PairId < 0)
{
return null;
}
for (int i = 0; i < _enemies.Count; i++)
{
EnemyInstance candidate = _enemies[i];
if (candidate != enemy
&& candidate.PairId == enemy.PairId
&& !candidate.State.IsDefeated)
{
return candidate;
}
}
return null;
}
private void DefendSelfAndAdjacent(int enemyIndex, int amount) private void DefendSelfAndAdjacent(int enemyIndex, int amount)
{ {
int applied = 0; int applied = 0;
@@ -1866,6 +2130,38 @@ namespace KernelPanic.Combat
return mitigated; return mitigated;
} }
private int ResolveDamageResistance(DamageRequest request, int amount)
{
EnemyInstance target = FindEnemy(request.Target);
if (target == null
|| !target.HasBehavior(EnemyBehaviorFlags.RootkitMasked)
|| !HasOtherLivingEnemy(target))
{
return amount;
}
if (amount <= 0)
{
return 0;
}
return Mathf.Max(1, Mathf.CeilToInt(amount * (EnemyArchetypeCatalog.RootkitMaskedDamagePercent / 100f)));
}
private bool HasOtherLivingEnemy(EnemyInstance target)
{
for (int i = 0; i < _enemies.Count; i++)
{
EnemyInstance enemy = _enemies[i];
if (enemy != target && enemy.State != null && !enemy.State.IsDefeated)
{
return true;
}
}
return false;
}
private void ResolveDelayedEndOfTurnEffects() private void ResolveDelayedEndOfTurnEffects()
{ {
for (int i = _delayedEffects.Count - 1; i >= 0; i--) for (int i = _delayedEffects.Count - 1; i >= 0; i--)
+12 -2
View File
@@ -37,11 +37,21 @@ namespace KernelPanic.Combat
public const int EntropyStartWave = 5; public const int EntropyStartWave = 5;
public const int EntropyPerWave = 3; public const int EntropyPerWave = 3;
public const int StatUpgradeMaxCycles = 1; public const int StatUpgradeMaxCycles = 1;
public const int StatUpgradeMaxUptime = 4; public const int StatUpgradeMaxUptimePercent = 20;
public const int StatUpgradeHeal = 5; public const int StatUpgradeHealPercent = 30;
public const int StatUpgradeRam = 1; public const int StatUpgradeRam = 1;
public const int UpgradeMagnitudeBonus = 2; public const int UpgradeMagnitudeBonus = 2;
public static int ScaleStatUpgradeMaxUptime(int maxUptime)
{
return System.Math.Max(1, ((System.Math.Max(1, maxUptime) * StatUpgradeMaxUptimePercent) + 99) / 100);
}
public static int ScaleStatUpgradeHeal(int maxUptime)
{
return System.Math.Max(1, ((System.Math.Max(1, maxUptime) * StatUpgradeHealPercent) + 99) / 100);
}
// Combat pacing: gaps between phase transitions and per-item resolution steps so // Combat pacing: gaps between phase transitions and per-item resolution steps so
// players can follow drawing/queue/enemy resolution instead of seeing it all at once. // players can follow drawing/queue/enemy resolution instead of seeing it all at once.
// Skipped entirely when UIPreferences.ReducedMotion is set. // Skipped entirely when UIPreferences.ReducedMotion is set.
+1 -1
View File
@@ -36,7 +36,7 @@ namespace KernelPanic.Combat
public int IncomingAttackHalfCharges { get; set; } public int IncomingAttackHalfCharges { get; set; }
public int ArchBtwStacks { get; set; } public int ArchBtwStacks { get; set; }
public int ArchBtwDamagePerStack { get; set; } = 1; public int ArchBtwDamagePerStack { get; set; } = 1;
public int ArchMakepkgBtwMultiplier { get; set; } = 2; public int ArchMakepkgBtwMultiplier { get; set; } = 3;
public int ArchRollingReleaseSavesRemaining { get; set; } public int ArchRollingReleaseSavesRemaining { get; set; }
public int ArchRollingReleaseShieldOnSave { get; set; } public int ArchRollingReleaseShieldOnSave { get; set; }
public int ArchRollingReleaseCyclesOnSave { get; set; } public int ArchRollingReleaseCyclesOnSave { get; set; }
+25 -5
View File
@@ -11,7 +11,8 @@ namespace KernelPanic.Combat
int amount, int amount,
Language language, Language language,
bool trueDamage, bool trueDamage,
bool canCrit) bool canCrit,
bool applySourceModifiers = true)
{ {
Source = source; Source = source;
Target = target; Target = target;
@@ -19,6 +20,7 @@ namespace KernelPanic.Combat
Language = language; Language = language;
TrueDamage = trueDamage; TrueDamage = trueDamage;
CanCrit = canCrit; CanCrit = canCrit;
ApplySourceModifiers = applySourceModifiers;
} }
public CombatantState Source { get; } public CombatantState Source { get; }
@@ -27,6 +29,7 @@ namespace KernelPanic.Combat
public Language Language { get; } public Language Language { get; }
public bool TrueDamage { get; } public bool TrueDamage { get; }
public bool CanCrit { get; } public bool CanCrit { get; }
public bool ApplySourceModifiers { get; }
} }
public readonly struct DamageResult public readonly struct DamageResult
@@ -56,6 +59,8 @@ namespace KernelPanic.Combat
/// </summary> /// </summary>
public sealed class DamagePipeline public sealed class DamagePipeline
{ {
public System.Func<DamageRequest, int, int> ResistanceResolver { get; set; }
public DamageResult DealDamage(DamageRequest request) public DamageResult DealDamage(DamageRequest request)
{ {
if (request.Target == null || request.Target.IsDefeated) if (request.Target == null || request.Target.IsDefeated)
@@ -64,9 +69,14 @@ namespace KernelPanic.Combat
} }
int amount = Mathf.Max(0, request.Amount); int amount = Mathf.Max(0, request.Amount);
bool wasCritical = false;
if (request.ApplySourceModifiers)
{
amount = ApplyFlatAdditions(amount, request); amount = ApplyFlatAdditions(amount, request);
amount = ApplyMultipliers(amount, request); amount = ApplyMultipliers(amount, request);
amount = ApplyCrit(amount, request, out bool wasCritical); amount = ApplyCrit(amount, request, out wasCritical);
}
amount = ApplyResistancesAndWeaknesses(amount, request); amount = ApplyResistancesAndWeaknesses(amount, request);
int finalAmount = ApplyShieldAndUptime(amount, request, out int absorbedAmount, out int shieldDamage, out int uptimeDamage); int finalAmount = ApplyShieldAndUptime(amount, request, out int absorbedAmount, out int shieldDamage, out int uptimeDamage);
@@ -86,6 +96,11 @@ namespace KernelPanic.Combat
private static int ApplyMultipliers(int amount, DamageRequest request) private static int ApplyMultipliers(int amount, DamageRequest request)
{ {
if (amount == int.MaxValue)
{
return amount;
}
if (request.Source != null && request.Source.IgnoreDamageMultipliers) if (request.Source != null && request.Source.IgnoreDamageMultipliers)
{ {
// Mint V1+ ignores multiplicative damage modifiers. Mint V4 still allows flat // Mint V1+ ignores multiplicative damage modifiers. Mint V4 still allows flat
@@ -100,6 +115,11 @@ namespace KernelPanic.Combat
private static int ApplyFlatAdditions(int amount, DamageRequest request) private static int ApplyFlatAdditions(int amount, DamageRequest request)
{ {
if (amount == int.MaxValue)
{
return amount;
}
if (request.Source != null && request.Language == Language.JavaScript) if (request.Source != null && request.Language == Language.JavaScript)
{ {
amount += Mathf.Max(0, request.Source.JavaScriptFlatDamageBonus); amount += Mathf.Max(0, request.Source.JavaScriptFlatDamageBonus);
@@ -116,7 +136,7 @@ namespace KernelPanic.Combat
private static int ApplyCrit(int amount, DamageRequest request, out bool wasCritical) private static int ApplyCrit(int amount, DamageRequest request, out bool wasCritical)
{ {
wasCritical = false; wasCritical = false;
if (!request.CanCrit || amount <= 0 || request.Source != null && request.Source.ForceMaxRolls) if (!request.CanCrit || amount <= 0 || amount == int.MaxValue || request.Source != null && request.Source.ForceMaxRolls)
{ {
return amount; return amount;
} }
@@ -144,12 +164,12 @@ namespace KernelPanic.Combat
target.ArchRollingReleaseRecoveredThisHit = true; target.ArchRollingReleaseRecoveredThisHit = true;
} }
private static int ApplyResistancesAndWeaknesses(int amount, DamageRequest request) private int ApplyResistancesAndWeaknesses(int amount, DamageRequest request)
{ {
// TODO: Enemy resistances/reductions are not authored yet. Mint V5 should re-resolve // 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. // 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 ResistanceResolver == null ? amount : ResistanceResolver(request, amount);
} }
private static int ApplyShieldAndUptime(int amount, DamageRequest request, out int absorbedAmount, out int shieldDamage, out int uptimeDamage) private static int ApplyShieldAndUptime(int amount, DamageRequest request, out int absorbedAmount, out int shieldDamage, out int uptimeDamage)
+66 -8
View File
@@ -14,7 +14,12 @@ namespace KernelPanic.Combat
Grow = 1 << 2, Grow = 1 << 2,
ObfuscateIntent = 1 << 3, ObfuscateIntent = 1 << 3,
DefendAllies = 1 << 4, DefendAllies = 1 << 4,
Countdown = 1 << 5 Countdown = 1 << 5,
LeavesOrphan = 1 << 6,
SegfaultOnDeath = 1 << 7,
RacePair = 1 << 8,
RootkitMasked = 1 << 9,
Elite = 1 << 10
} }
public sealed class EnemyArchetypeDescriptor public sealed class EnemyArchetypeDescriptor
@@ -51,18 +56,23 @@ namespace KernelPanic.Combat
{ {
public const int ZombieReapThresholdPercent = 30; public const int ZombieReapThresholdPercent = 30;
public const int MemoryLeakAttackGrowthPerTurn = 1; public const int MemoryLeakAttackGrowthPerTurn = 1;
public const int MemoryLeakAttackCapMin = 4;
public const int MemoryLeakAttackCapMax = 5;
public const int ForkBombTotalCap = 5; public const int ForkBombTotalCap = 5;
public const int RootkitMaskedDamagePercent = 10;
public const int RacePairDamageBonus = 2;
public const int CronInitialCountdown = 2; public const int CronInitialCountdown = 2;
public const int SegfaultInitialCountdown = 2;
private static readonly EnemyArchetypeDescriptor ZombieProcess = new( private static readonly EnemyArchetypeDescriptor ZombieProcess = new(
"zombie_process", "zombie_process",
"Zombie Process", "Zombie Process",
6, 6,
10, 10,
EnemyBehaviorFlags.Revive, EnemyBehaviorFlags.Revive | EnemyBehaviorFlags.LeavesOrphan,
new[] new[]
{ {
new EnemyIntent(EnemyIntentKind.Attack, 3, 4, Language.C, "attack", "!") new EnemyIntent(EnemyIntentKind.Attack, 2, 3, Language.C, "attack", "!")
}); });
private static readonly EnemyArchetypeDescriptor MemoryLeak = new( private static readonly EnemyArchetypeDescriptor MemoryLeak = new(
@@ -73,7 +83,7 @@ namespace KernelPanic.Combat
EnemyBehaviorFlags.Grow, EnemyBehaviorFlags.Grow,
new[] new[]
{ {
new EnemyIntent(EnemyIntentKind.StatusAttack, 1, 1, Language.C, "leak", "!", false, StatusType.MemoryLeak, 1, CombatTuning.EnemyStatusDuration) new EnemyIntent(EnemyIntentKind.StatusAttack, 1, 2, Language.C, "leak", "!", false, StatusType.MemoryLeak, 1, CombatTuning.EnemyStatusDuration)
}); });
private static readonly EnemyArchetypeDescriptor ForkBomb = new( private static readonly EnemyArchetypeDescriptor ForkBomb = new(
@@ -81,10 +91,10 @@ namespace KernelPanic.Combat
"Fork Bomb", "Fork Bomb",
3, 3,
5, 5,
EnemyBehaviorFlags.Split, EnemyBehaviorFlags.Split | EnemyBehaviorFlags.LeavesOrphan,
new[] new[]
{ {
new EnemyIntent(EnemyIntentKind.Special, 0, 0, Language.C, "fork", ":", false, StatusType.MemoryLeak, 0, -1, "split") new EnemyIntent(EnemyIntentKind.Attack, 1, 2, Language.C, "fork hit", "!")
}); });
private static readonly EnemyArchetypeDescriptor Daemon = new( private static readonly EnemyArchetypeDescriptor Daemon = new(
@@ -92,7 +102,7 @@ namespace KernelPanic.Combat
"Daemon", "Daemon",
10, 10,
14, 14,
EnemyBehaviorFlags.ObfuscateIntent, EnemyBehaviorFlags.ObfuscateIntent | EnemyBehaviorFlags.Elite,
new[] new[]
{ {
new EnemyIntent(EnemyIntentKind.Attack, 6, 8, Language.C, "daemon hit", "!") new EnemyIntent(EnemyIntentKind.Attack, 6, 8, Language.C, "daemon hit", "!")
@@ -108,7 +118,7 @@ namespace KernelPanic.Combat
{ {
new EnemyIntent(EnemyIntentKind.Defend, 3, 5, Language.C, "shield adjacent", "#"), new EnemyIntent(EnemyIntentKind.Defend, 3, 5, Language.C, "shield adjacent", "#"),
new EnemyIntent(EnemyIntentKind.Defend, 3, 5, Language.C, "shield adjacent", "#"), new EnemyIntent(EnemyIntentKind.Defend, 3, 5, Language.C, "shield adjacent", "#"),
new EnemyIntent(EnemyIntentKind.Attack, 1, 2, Language.C, "poke", "!") new EnemyIntent(EnemyIntentKind.Attack, 0, 1, Language.C, "poke", "!")
}); });
private static readonly EnemyArchetypeDescriptor CronJob = new( private static readonly EnemyArchetypeDescriptor CronJob = new(
@@ -122,6 +132,50 @@ namespace KernelPanic.Combat
new EnemyIntent(EnemyIntentKind.Attack, 10, 12, Language.C, "cron fire", "@") new EnemyIntent(EnemyIntentKind.Attack, 10, 12, Language.C, "cron fire", "@")
}); });
private static readonly EnemyArchetypeDescriptor OrphanProcess = new(
"orphan_process",
"Orphan Process",
2,
4,
EnemyBehaviorFlags.None,
new[]
{
new EnemyIntent(EnemyIntentKind.Attack, 1, 2, Language.C, "orphan chip", ".")
});
private static readonly EnemyArchetypeDescriptor Segfault = new(
"segfault",
"Segfault",
2,
4,
EnemyBehaviorFlags.SegfaultOnDeath,
new[]
{
new EnemyIntent(EnemyIntentKind.Attack, 2, 3, Language.C, "unstable hit", "!")
});
private static readonly EnemyArchetypeDescriptor RaceCondition = new(
"race_condition",
"Race Condition",
5,
7,
EnemyBehaviorFlags.RacePair,
new[]
{
new EnemyIntent(EnemyIntentKind.Attack, 2, 3, Language.C, "racy strike", "~")
});
private static readonly EnemyArchetypeDescriptor Rootkit = new(
"rootkit",
"Rootkit",
14,
18,
EnemyBehaviorFlags.RootkitMasked,
new[]
{
new EnemyIntent(EnemyIntentKind.Attack, 1, 2, Language.C, "masked hit", "?")
});
public static EnemyArchetypeDescriptor Get(string id) public static EnemyArchetypeDescriptor Get(string id)
{ {
return id switch return id switch
@@ -131,6 +185,10 @@ namespace KernelPanic.Combat
"daemon" => Daemon, "daemon" => Daemon,
"firewalld" => Defender, "firewalld" => Defender,
"cron_job" => CronJob, "cron_job" => CronJob,
"orphan_process" => OrphanProcess,
"segfault" => Segfault,
"race_condition" => RaceCondition,
"rootkit" => Rootkit,
_ => ZombieProcess _ => ZombieProcess
}; };
} }
+44 -2
View File
@@ -24,7 +24,9 @@ namespace KernelPanic.Combat
_intentPool.AddRange(Archetype.IntentPool); _intentPool.AddRange(Archetype.IntentPool);
} }
CountdownRemaining = HasBehavior(EnemyBehaviorFlags.Countdown) ? EnemyArchetypeCatalog.CronInitialCountdown : 0; CountdownRemaining = HasBehavior(EnemyBehaviorFlags.Countdown)
? EnemyArchetypeCatalog.CronInitialCountdown
: HasBehavior(EnemyBehaviorFlags.SegfaultOnDeath) ? EnemyArchetypeCatalog.SegfaultInitialCountdown : 0;
} }
public string Name { get; } public string Name { get; }
@@ -42,7 +44,16 @@ namespace KernelPanic.Combat
public bool PendingRevive { get; private set; } public bool PendingRevive { get; private set; }
public bool Reaped { get; private set; } public bool Reaped { get; private set; }
public bool LethalHitThisTurn { get; private set; } public bool LethalHitThisTurn { get; private set; }
public int PairId { get; private set; } = -1;
public int RaceEnrageStacks { get; private set; }
public bool SpawnedFromDeath { get; private set; }
public bool HasPendingMarker => PendingRevive || HasRevived; public bool HasPendingMarker => PendingRevive || HasRevived;
public bool HasSpecialSignal => HasSplitSignal || HasSegfaultSignal || HasRaceSignal || HasRootkitSignal || HasEliteSignal || SpawnedFromDeath;
public bool HasSplitSignal => HasBehavior(EnemyBehaviorFlags.Split);
public bool HasSegfaultSignal => HasBehavior(EnemyBehaviorFlags.SegfaultOnDeath);
public bool HasRaceSignal => HasBehavior(EnemyBehaviorFlags.RacePair);
public bool HasRootkitSignal => HasBehavior(EnemyBehaviorFlags.RootkitMasked);
public bool HasEliteSignal => HasBehavior(EnemyBehaviorFlags.Elite);
public EnemyIntent DisplayIntent => BuildDisplayIntent(); public EnemyIntent DisplayIntent => BuildDisplayIntent();
public void PickNextIntent() public void PickNextIntent()
@@ -61,6 +72,12 @@ namespace KernelPanic.Combat
return; return;
} }
if (HasBehavior(EnemyBehaviorFlags.SegfaultOnDeath) && CountdownRemaining > 0)
{
CurrentIntent = new EnemyIntent(EnemyIntentKind.Special, CountdownRemaining, CountdownRemaining, KernelPanic.Core.Language.C, "segfault in", "!");
return;
}
if (_intentPool.Count == 0) if (_intentPool.Count == 0)
{ {
CurrentIntent = default; CurrentIntent = default;
@@ -72,7 +89,9 @@ namespace KernelPanic.Combat
if (HasBehavior(EnemyBehaviorFlags.Grow)) if (HasBehavior(EnemyBehaviorFlags.Grow))
{ {
int growth = TurnsAlive * EnemyArchetypeCatalog.MemoryLeakAttackGrowthPerTurn; int growth = TurnsAlive * EnemyArchetypeCatalog.MemoryLeakAttackGrowthPerTurn;
CurrentIntent = CurrentIntent.WithValues(CurrentIntent.MinValue + growth, CurrentIntent.MaxValue + growth, "growing leak"); int minValue = UnityEngine.Mathf.Min(CurrentIntent.MinValue + growth, EnemyArchetypeCatalog.MemoryLeakAttackCapMin);
int maxValue = UnityEngine.Mathf.Min(CurrentIntent.MaxValue + growth, EnemyArchetypeCatalog.MemoryLeakAttackCapMax);
CurrentIntent = CurrentIntent.WithValues(minValue, maxValue, "growing leak");
} }
} }
@@ -94,6 +113,21 @@ namespace KernelPanic.Combat
TurnsAlive++; TurnsAlive++;
} }
public void SetPairId(int pairId)
{
PairId = pairId;
}
public void MarkSpawnedFromDeath()
{
SpawnedFromDeath = true;
}
public void EnrageRaceSurvivor()
{
RaceEnrageStacks++;
}
public void ResetTurnLethalMarker() public void ResetTurnLethalMarker()
{ {
LethalHitThisTurn = false; LethalHitThisTurn = false;
@@ -142,6 +176,14 @@ namespace KernelPanic.Combat
: UnityEngine.Mathf.Max(0, CountdownRemaining - 1); : UnityEngine.Mathf.Max(0, CountdownRemaining - 1);
} }
public void AdvanceSegfaultCountdown()
{
if (HasBehavior(EnemyBehaviorFlags.SegfaultOnDeath))
{
CountdownRemaining = UnityEngine.Mathf.Max(0, CountdownRemaining - 1);
}
}
private EnemyIntent BuildDisplayIntent() private EnemyIntent BuildDisplayIntent()
{ {
if (PendingRevive) if (PendingRevive)
@@ -13,7 +13,7 @@ namespace KernelPanic.Combat
private static readonly Dictionary<StatusType, StatusDescriptor> Descriptors = new() private static readonly Dictionary<StatusType, StatusDescriptor> Descriptors = new()
{ {
[StatusType.MemoryLeak] = new(StatusType.MemoryLeak, "Memory Leak", "leak", false, StatusStackingRule.Intensity, StatusTickTiming.StartOfTurn, "Takes damage equal to stacks at start of turn."), [StatusType.MemoryLeak] = new(StatusType.MemoryLeak, "Memory Leak", "leak", false, StatusStackingRule.Intensity, StatusTickTiming.StartOfTurn, "Takes damage equal to stacks at start of turn."),
[StatusType.Segfault] = new(StatusType.Segfault, "Segfault", "segv", false, StatusStackingRule.Unique, StatusTickTiming.StartOfTurn, "Loses one cycle this turn. TODO: lock a random card slot."), [StatusType.Segfault] = new(StatusType.Segfault, "Segfault", "segv", false, StatusStackingRule.Unique, StatusTickTiming.StartOfTurn, "Loses one cycle this turn. Segfault events corrupt a random hand card until fixed."),
[StatusType.RaceCondition] = new(StatusType.RaceCondition, "Race Condition", "race", false, StatusStackingRule.Refresh, StatusTickTiming.None, "Randomizes multi-target resolution order where supported."), [StatusType.RaceCondition] = new(StatusType.RaceCondition, "Race Condition", "race", false, StatusStackingRule.Refresh, StatusTickTiming.None, "Randomizes multi-target resolution order where supported."),
[StatusType.Deprecated] = new(StatusType.Deprecated, "Deprecated", "dep", false, StatusStackingRule.Duration, StatusTickTiming.StartOfTurn, "A temporary positive value decays. Currently reduces shield by 1."), [StatusType.Deprecated] = new(StatusType.Deprecated, "Deprecated", "dep", false, StatusStackingRule.Duration, StatusTickTiming.StartOfTurn, "A temporary positive value decays. Currently reduces shield by 1."),
[StatusType.DependencyError] = new(StatusType.DependencyError, "Dependency Error", "deps", false, StatusStackingRule.Unique, StatusTickTiming.None, "Registered for off-support card cost rules. TODO: connect draw-time cost path."), [StatusType.DependencyError] = new(StatusType.DependencyError, "Dependency Error", "deps", false, StatusStackingRule.Unique, StatusTickTiming.None, "Registered for off-support card cost rules. TODO: connect draw-time cost path."),
+2 -2
View File
@@ -131,8 +131,8 @@ namespace KernelPanic.Run
return upgradeKind switch return upgradeKind switch
{ {
RunStatUpgradeKind.MaxCycles => $"+{CombatTuning.StatUpgradeMaxCycles} max cycles this run", RunStatUpgradeKind.MaxCycles => $"+{CombatTuning.StatUpgradeMaxCycles} max cycles this run",
RunStatUpgradeKind.MaxUptime => $"+{CombatTuning.StatUpgradeMaxUptime} max uptime this run", RunStatUpgradeKind.MaxUptime => $"+{CombatTuning.StatUpgradeMaxUptimePercent}% max uptime this run",
RunStatUpgradeKind.Heal => $"heal {CombatTuning.StatUpgradeHeal} uptime now", RunStatUpgradeKind.Heal => $"heal {CombatTuning.StatUpgradeHealPercent}% max uptime now",
RunStatUpgradeKind.Ram => $"+{CombatTuning.StatUpgradeRam} RAM this run", RunStatUpgradeKind.Ram => $"+{CombatTuning.StatUpgradeRam} RAM this run",
_ => "--" _ => "--"
}; };
+5 -3
View File
@@ -496,10 +496,11 @@ namespace KernelPanic.Run
} }
return true; return true;
case RunStatUpgradeKind.MaxUptime: case RunStatUpgradeKind.MaxUptime:
_maxUptimeBonus += CombatTuning.StatUpgradeMaxUptime; int uptimeBonus = CombatTuning.ScaleStatUpgradeMaxUptime(playerState == null ? EffectiveMaxUptime() : playerState.MaxUptime);
_maxUptimeBonus += uptimeBonus;
if (playerState != null) if (playerState != null)
{ {
playerState.MaxUptime += CombatTuning.StatUpgradeMaxUptime; playerState.MaxUptime += uptimeBonus;
} }
return true; return true;
case RunStatUpgradeKind.Heal: case RunStatUpgradeKind.Heal:
@@ -508,7 +509,8 @@ namespace KernelPanic.Run
return false; return false;
} }
playerState.CurrentUptime = Mathf.Min(playerState.MaxUptime, playerState.CurrentUptime + CombatTuning.StatUpgradeHeal); int healAmount = CombatTuning.ScaleStatUpgradeHeal(playerState.MaxUptime);
playerState.CurrentUptime = Mathf.Min(playerState.MaxUptime, playerState.CurrentUptime + healAmount);
return true; return true;
case RunStatUpgradeKind.Ram: case RunStatUpgradeKind.Ram:
_ramBonus += CombatTuning.StatUpgradeRam; _ramBonus += CombatTuning.StatUpgradeRam;
+119 -2
View File
@@ -12,7 +12,8 @@ namespace KernelPanic.UI
{ {
/// <summary> /// <summary>
/// Runtime-built GameScene combat UI. Intent icons use a compact terminal vocabulary: /// Runtime-built GameScene combat UI. Intent icons use a compact terminal vocabulary:
/// ! attack/status attack, # defend, + buff, * special, ? hidden, ~ reviving, : split, @ countdown. /// ! attack/status attack/segfault, # defend, + buff, * special, ? hidden/rootkit,
/// ~ reviving/race link, : split, @ countdown, . orphan.
/// </summary> /// </summary>
[RequireComponent(typeof(UIDocument))] [RequireComponent(typeof(UIDocument))]
[RequireComponent(typeof(RunManager))] [RequireComponent(typeof(RunManager))]
@@ -276,9 +277,12 @@ namespace KernelPanic.UI
VisualElement enemiesPanel = CreatePanel("enemy processes", "intent telemetry", 438); VisualElement enemiesPanel = CreatePanel("enemy processes", "intent telemetry", 438);
main.Add(enemiesPanel); main.Add(enemiesPanel);
ScrollView enemyScroll = new(ScrollViewMode.Vertical);
enemyScroll.AddToClassList("enemy-scroll");
enemiesPanel.Add(enemyScroll);
_enemyRow = new(); _enemyRow = new();
_enemyRow.AddToClassList("enemy-row"); _enemyRow.AddToClassList("enemy-row");
enemiesPanel.Add(_enemyRow); enemyScroll.Add(_enemyRow);
VisualElement bottom = new(); VisualElement bottom = new();
bottom.AddToClassList("bottom-layout"); bottom.AddToClassList("bottom-layout");
@@ -445,6 +449,9 @@ namespace KernelPanic.UI
{ {
_enemyRow.Clear(); _enemyRow.Clear();
_combatantElements.Clear(); _combatantElements.Clear();
int enemyCount = _combatManager.Enemies.Count;
bool compactEnemies = enemyCount >= 5;
_enemyRow.EnableInClassList("enemy-row-compact", compactEnemies);
if (_combatManager.PlayerState != null) if (_combatManager.PlayerState != null)
{ {
_combatantElements[_combatManager.PlayerState] = _playerPanel; _combatantElements[_combatManager.PlayerState] = _playerPanel;
@@ -457,6 +464,7 @@ namespace KernelPanic.UI
Button card = new(() => _combatManager.SelectEnemy(index)); Button card = new(() => _combatManager.SelectEnemy(index));
card.text = string.Empty; card.text = string.Empty;
card.AddToClassList("enemy-card"); card.AddToClassList("enemy-card");
card.EnableInClassList("enemy-card-compact", compactEnemies);
bool highlighted = _combatManager.PendingTargetCard != null || _combatManager.SelectedEnemyIndex == index; bool highlighted = _combatManager.PendingTargetCard != null || _combatManager.SelectedEnemyIndex == index;
if (highlighted) if (highlighted)
{ {
@@ -473,6 +481,11 @@ namespace KernelPanic.UI
card.Add(marker); card.Add(marker);
} }
if (enemy.HasSpecialSignal)
{
card.Add(EnemySignalPanel(enemy, _combatManager.Enemies));
}
_combatantElements[enemy.State] = card; _combatantElements[enemy.State] = card;
DetectBeneficialResourceFeedback(enemy.State, card); DetectBeneficialResourceFeedback(enemy.State, card);
ApplyStatusStateClasses(card, enemy.State); ApplyStatusStateClasses(card, enemy.State);
@@ -823,6 +836,12 @@ namespace KernelPanic.UI
button.AddToClassList("hand-card-unaffordable"); button.AddToClassList("hand-card-unaffordable");
} }
if (card.IsBroken || card.IsLocked)
{
button.AddToClassList("hand-card-unaffordable");
button.SetEnabled(false);
}
PopulateCardFace(button, card); PopulateCardFace(button, card);
return button; return button;
} }
@@ -853,6 +872,13 @@ namespace KernelPanic.UI
top.Add(name); top.Add(name);
target.Add(top); target.Add(top);
if (card.IsBroken || card.IsLocked)
{
Label state = new(card.IsBroken ? "segv corrupt" : "locked");
state.AddToClassList("status-label");
target.Add(state);
}
VisualElement tags = new(); VisualElement tags = new();
tags.AddToClassList("tag-row"); tags.AddToClassList("tag-row");
tags.Add(Tag(card.Definition.Language.ToString(), LanguageClass(card.Definition.Language))); tags.Add(Tag(card.Definition.Language.ToString(), LanguageClass(card.Definition.Language)));
@@ -885,6 +911,97 @@ namespace KernelPanic.UI
return panel; return panel;
} }
private static VisualElement EnemySignalPanel(EnemyInstance enemy, IReadOnlyList<EnemyInstance> enemies)
{
VisualElement row = new();
row.AddToClassList("status-row");
if (enemy.SpawnedFromDeath)
{
row.Add(Tag(". orphaned", "track-tag"));
}
if (enemy.HasEliteSignal)
{
row.Add(Tag("! elite", "intent-attack"));
}
if (enemy.HasSplitSignal)
{
string text = CountLivingArchetype(enemies, enemy.ArchetypeId) >= EnemyArchetypeCatalog.ForkBombTotalCap
? ": split capped"
: ": split next";
row.Add(Tag(text, "track-tag"));
}
if (enemy.HasSegfaultSignal)
{
row.Add(Tag($"! corrupt in {enemy.CountdownRemaining}", "track-tag"));
}
if (enemy.HasRaceSignal)
{
string text = HasLivingPair(enemies, enemy) ? "~ linked random" : "~ unlinked";
row.Add(Tag(text, "track-tag"));
}
if (enemy.HasRootkitSignal)
{
string text = HasOtherLivingEnemy(enemies, enemy) ? "? masked reduced" : "? exposed";
row.Add(Tag(text, "track-tag"));
}
return row;
}
private static int CountLivingArchetype(IReadOnlyList<EnemyInstance> enemies, string archetypeId)
{
int count = 0;
for (int i = 0; i < enemies.Count; i++)
{
EnemyInstance enemy = enemies[i];
if (enemy.ArchetypeId == archetypeId && !enemy.State.IsDefeated)
{
count++;
}
}
return count;
}
private static bool HasLivingPair(IReadOnlyList<EnemyInstance> enemies, EnemyInstance target)
{
if (target == null || target.PairId < 0)
{
return false;
}
for (int i = 0; i < enemies.Count; i++)
{
EnemyInstance enemy = enemies[i];
if (enemy != target && enemy.PairId == target.PairId && !enemy.State.IsDefeated)
{
return true;
}
}
return false;
}
private static bool HasOtherLivingEnemy(IReadOnlyList<EnemyInstance> enemies, EnemyInstance target)
{
for (int i = 0; i < enemies.Count; i++)
{
EnemyInstance enemy = enemies[i];
if (enemy != target && !enemy.State.IsDefeated)
{
return true;
}
}
return false;
}
private static VisualElement MeterBlock(string name, int current, int max, MeterTone tone) private static VisualElement MeterBlock(string name, int current, int max, MeterTone tone)
{ {
float ratio = max <= 0 ? 0f : Mathf.Clamp01((float)current / max); float ratio = max <= 0 ? 0f : Mathf.Clamp01((float)current / max);