Follow-up pass on the 2.0 refactor, closing the gap between "waves are drawn at random" and "difficulty still lives in the enemy assets". - Enemy health is no longer authored per enemy type. New EnemyScalingConfig scales a per-zone HP *budget* by encounter number; per-enemy health is that budget divided by the wave's enemy count. Scaling the total rather than the per-enemy value keeps enemy count a texture knob (few tanks vs many swarmers) instead of a second, uncontrolled difficulty axis. EnemyDefinition.MaxHp becomes HpMultiplier, a deviation around 1.0. Speed stays archetype-only and unscaled -- escalating it compounds with health and invalidates tower balance mid-run. - GoldConfig entries no longer reference a WaveDefinition. Payout is a property of run position, not of which wave got drawn: WaveGoldEntry -> EncounterGoldEntry, Waves -> Encounters, keyed by global encounter number. Its inspector labels elements "Encounter N" and projects cumulative earnings. - The wave's voted buffs now show as icon badges in the top bar, read from the same RunState slot the spawn path uses so the display can't drift from what the enemies actually carry. Hover names the buff; unillustrated cards fall back to a lettered badge rather than vanishing. - Clearing a phase's boss destroys every built tower, unrefunded. Queued build jobs still refund -- those towers were never delivered. Runs with the field empty, so the walkability churn doesn't hit the re-path scheduler. - Fixed an RPC codegen break: [ClientRpc] requires a ClientRpc suffix, unlike the newer [Rpc(SendTo...)] style this file doesn't use. - Setup checklist reordered into dependency order; it previously asked for a RunDefinition two sections before creating one. Also carries the editor-side asset reorganisation into Definitions/RunDefinitions and the sprite move into Enemy/Player draft icon folders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
608 lines
15 KiB
Text
608 lines
15 KiB
Text
/* ============================================================
|
|
HUD.uss — Match HUD stylesheet
|
|
Layout mirrors wintermaul_ui_v4.html; visual style is
|
|
placeholder and will be replaced in Phase 2.
|
|
============================================================ */
|
|
|
|
/* Root: covers full screen, transparent so the 3D scene shows through.
|
|
PickingMode.Ignore is set in C# so clicks in the map area reach the scene. */
|
|
.hud-root {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
flex-direction: column;
|
|
background-color: rgba(0, 0, 0, 0);
|
|
}
|
|
|
|
/* ---- TOP BAR ------------------------------------------------ */
|
|
|
|
.top-bar {
|
|
height: 28px;
|
|
flex-shrink: 0;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
padding: 0 10px;
|
|
background-color: rgba(22, 20, 10, 0.92);
|
|
border-bottom-width: 1px;
|
|
border-bottom-color: rgba(90, 80, 32, 1);
|
|
}
|
|
|
|
.res-group {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
|
|
.gold-label,
|
|
.income-label,
|
|
.wave-label,
|
|
.dimmed-label {
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.top-spacer {
|
|
flex: 1;
|
|
}
|
|
|
|
/* Gear button at the far right of the top bar. Explicit min/max width and zero
|
|
padding override the default Button theme, which would otherwise pad it out
|
|
to a text-sized control. background-image: none is required too — the default
|
|
runtime theme skins Button with a light rounded sprite, and setting only
|
|
background-color leaves that sprite on top (renders as a blank pale square). */
|
|
.menu-button {
|
|
width: 22px;
|
|
height: 22px;
|
|
min-width: 22px;
|
|
min-height: 22px;
|
|
max-width: 22px;
|
|
flex-shrink: 0;
|
|
margin: 0 0 0 12px;
|
|
padding: 0;
|
|
border-width: 1px;
|
|
border-color: rgba(90, 80, 32, 1);
|
|
background-image: none;
|
|
background-color: rgba(0, 0, 0, 0.25);
|
|
}
|
|
|
|
.menu-button:hover {
|
|
background-color: rgba(90, 80, 32, 0.55);
|
|
}
|
|
|
|
.gold-label {
|
|
color: rgb(255, 224, 102);
|
|
font-size: 12px;
|
|
-unity-font-style: bold;
|
|
}
|
|
|
|
.income-label {
|
|
color: rgb(136, 204, 255);
|
|
font-size: 11px;
|
|
}
|
|
|
|
.wave-label {
|
|
color: rgb(255, 170, 170);
|
|
font-size: 11px;
|
|
-unity-font-style: bold;
|
|
background-color: rgba(58, 26, 26, 0.85);
|
|
border-width: 1px;
|
|
border-color: rgba(138, 58, 58, 1);
|
|
border-radius: 3px;
|
|
padding: 1px 7px;
|
|
}
|
|
|
|
.dimmed-label {
|
|
color: rgb(136, 136, 136);
|
|
font-size: 11px;
|
|
}
|
|
|
|
/* ---- WAVE BUFF ICONS ----------------------------------------- */
|
|
/* Small badges in the top bar showing what the spawning wave carries.
|
|
Sized to sit inside the 28px top bar without stretching it. */
|
|
|
|
.wave-buff-row {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
|
|
.wave-buff-icon {
|
|
width: 18px;
|
|
height: 18px;
|
|
margin-right: 3px;
|
|
border-width: 1px;
|
|
border-color: rgba(138, 58, 58, 1);
|
|
border-radius: 3px;
|
|
background-color: rgba(58, 26, 26, 0.85);
|
|
}
|
|
|
|
/* Shown in place of the sprite when a card has no icon authored yet, so an
|
|
unillustrated buff is still visible and countable rather than invisible. */
|
|
.wave-buff-fallback {
|
|
-unity-text-align: middle-center;
|
|
-unity-font-style: bold;
|
|
font-size: 10px;
|
|
color: rgb(255, 170, 170);
|
|
}
|
|
|
|
/* ---- MAIN AREA ---------------------------------------------- */
|
|
|
|
.main-area {
|
|
flex: 1;
|
|
flex-direction: row;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* Transparent viewport area. PickingMode set to Ignore in C#. */
|
|
.map-area {
|
|
flex: 1;
|
|
background-color: rgba(0,0,0,0);
|
|
}
|
|
|
|
.rejection-label {
|
|
position: absolute;
|
|
bottom: 60px;
|
|
left: 0;
|
|
right: 0;
|
|
-unity-text-align: upper-center;
|
|
color: rgb(255, 90, 90);
|
|
font-size: 14px;
|
|
-unity-font-style: bold;
|
|
background-color: rgba(0, 0, 0, 0.65);
|
|
padding: 4px 14px;
|
|
border-radius: 4px;
|
|
display: none;
|
|
}
|
|
|
|
/* ---- RIGHT SCOREBOARD PANEL --------------------------------- */
|
|
|
|
.right-panel {
|
|
width: 172px;
|
|
flex-shrink: 0;
|
|
align-self: flex-start;
|
|
flex-direction: column;
|
|
background-color: rgb(22, 22, 16);
|
|
border-left-width: 1px;
|
|
border-left-color: rgba(58, 58, 32, 1);
|
|
border-bottom-width: 1px;
|
|
border-bottom-color: rgba(58, 58, 32, 1);
|
|
border-radius: 0 0 0 3px;
|
|
}
|
|
|
|
.panel-header {
|
|
padding: 4px 6px;
|
|
font-size: 10px;
|
|
color: rgb(255, 224, 102);
|
|
-unity-font-style: bold;
|
|
-unity-text-align: upper-center;
|
|
background-color: rgb(42, 36, 16);
|
|
border-bottom-width: 1px;
|
|
border-bottom-color: rgba(58, 58, 32, 1);
|
|
}
|
|
|
|
.section-label {
|
|
padding: 4px 6px 2px;
|
|
font-size: 9px;
|
|
color: rgb(102, 102, 80);
|
|
}
|
|
|
|
.player-list {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.income-row {
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
padding: 2px 6px;
|
|
}
|
|
|
|
.income-key {
|
|
font-size: 10px;
|
|
color: rgb(136, 136, 112);
|
|
}
|
|
|
|
.income-val {
|
|
font-size: 10px;
|
|
color: rgb(255, 224, 102);
|
|
}
|
|
|
|
/* ---- BOTTOM UI STRIP ---------------------------------------- */
|
|
/* Five separate framed sections, left to right:
|
|
1. Minimap — tall frame, 180w x 220h
|
|
2. Portrait — short frame, 90w x 165h (bottom-aligned)
|
|
3. Info Panel — short frame, 280w x 165h
|
|
4. Tool Tip — short frame, 120w x 165h
|
|
5. Action Menu — tall frame, 320w x 220h (hides via display:none
|
|
when no actions are available for the selection)
|
|
Sections sit on a transparent strip with 100px margins on each side; the
|
|
3D scene shows through above and beside them. */
|
|
|
|
.bottom-ui {
|
|
height: 220px; /* matches the tall sections */
|
|
flex-shrink: 0;
|
|
flex-direction: row;
|
|
align-items: flex-end; /* short sections hug the bottom */
|
|
justify-content: center; /* keep the command bar centered at ANY resolution — the row
|
|
auto-centers, and re-centers symmetrically when the spell
|
|
hotbar appears/disappears */
|
|
padding: 0; /* was 0 100px; reclaimed so a smaller reference resolution
|
|
(= bigger HUD) still fits the bar without clipping */
|
|
background-color: rgba(0, 0, 0, 0);
|
|
}
|
|
|
|
/* ----- Section 1: Minimap (tall) ----- */
|
|
.minimap-frame {
|
|
width: 180px;
|
|
height: 220px;
|
|
flex-shrink: 0;
|
|
padding: 4px;
|
|
background-color: rgb(35, 30, 15);
|
|
border-width: 3px;
|
|
border-color: rgb(120, 100, 50);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
/* MinimapView injects .minimap-terrain and .minimap-entities into this. */
|
|
.minimap {
|
|
flex: 1;
|
|
background-color: rgb(8, 10, 12);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.minimap-terrain {
|
|
position: absolute;
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
-unity-background-scale-mode: stretch-to-fill;
|
|
}
|
|
|
|
.minimap-entities {
|
|
position: absolute;
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
}
|
|
|
|
/* ----- Section 2: Portrait (short, bottom-aligned) ----- */
|
|
.portrait-frame {
|
|
width: 90px;
|
|
height: 165px;
|
|
flex-shrink: 0;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 6px;
|
|
background-color: rgb(35, 30, 15);
|
|
border-width: 2px;
|
|
border-color: rgb(80, 65, 35);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.portrait-image {
|
|
width: 74px;
|
|
height: 110px;
|
|
background-color: rgb(13, 26, 13);
|
|
border-width: 1px;
|
|
border-color: rgb(58, 90, 58);
|
|
border-radius: 3px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.level-label {
|
|
font-size: 11px;
|
|
color: rgb(220, 220, 200);
|
|
-unity-text-align: upper-center;
|
|
margin-top: 6px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* ----- Section 3: Info Panel (short, name + stat lines) ----- */
|
|
.info-frame {
|
|
width: 280px;
|
|
height: 165px;
|
|
flex-shrink: 0;
|
|
flex-direction: column;
|
|
padding: 8px 10px;
|
|
background-color: rgb(15, 15, 15);
|
|
border-width: 2px;
|
|
border-color: rgb(80, 65, 35);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.portrait-name {
|
|
font-size: 14px;
|
|
color: rgb(255, 224, 102);
|
|
-unity-font-style: bold;
|
|
-unity-text-align: upper-left;
|
|
margin-bottom: 8px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.stat-lines {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.stat-line {
|
|
font-size: 11px;
|
|
color: rgb(220, 220, 200);
|
|
-unity-text-align: upper-left;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
/* Build-progress block — shown in the info panel only when a BuildSiteVisual
|
|
is selected. Thin horizontal bar with a left-anchored fill, percent below. */
|
|
.build-progress {
|
|
flex-direction: column;
|
|
margin-top: 6px;
|
|
}
|
|
|
|
.build-progress-bg {
|
|
height: 10px;
|
|
background-color: rgb(30, 30, 24);
|
|
border-width: 1px;
|
|
border-color: rgb(80, 65, 35);
|
|
border-radius: 2px;
|
|
margin-bottom: 4px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* The fill is anchored to the LEFT side via absolute positioning + 0 right
|
|
margin set in code (width is driven each frame from progress). Color is the
|
|
same green family as the selection ring for visual consistency. */
|
|
.build-progress-fill {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 0%;
|
|
background-color: rgb(120, 220, 120);
|
|
}
|
|
|
|
.build-progress-percent {
|
|
font-size: 11px;
|
|
color: rgb(220, 220, 200);
|
|
-unity-text-align: upper-left;
|
|
}
|
|
|
|
/* ----- Section 4: Tool Tip (short, hover-driven) ----- */
|
|
.tooltip-frame {
|
|
width: 120px;
|
|
height: 165px;
|
|
flex-shrink: 0;
|
|
flex-direction: column;
|
|
padding: 8px;
|
|
background-color: rgb(15, 15, 15);
|
|
border-width: 2px;
|
|
border-color: rgb(80, 65, 35);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.tt-title {
|
|
font-size: 12px;
|
|
color: rgb(255, 224, 102);
|
|
-unity-font-style: bold;
|
|
margin-bottom: 4px;
|
|
white-space: normal;
|
|
}
|
|
|
|
.tt-desc {
|
|
font-size: 10px;
|
|
color: rgb(170, 168, 144);
|
|
white-space: normal;
|
|
}
|
|
|
|
.tt-stats {
|
|
font-size: 10px;
|
|
color: rgb(136, 170, 136);
|
|
margin-top: 3px;
|
|
white-space: normal;
|
|
}
|
|
|
|
.tt-cost {
|
|
font-size: 10px;
|
|
color: rgb(255, 224, 102);
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* ----- Section 5: Action Menu (tall, hides when no actions are available) ----- */
|
|
.action-frame {
|
|
width: 320px;
|
|
height: 220px;
|
|
flex-shrink: 0;
|
|
padding: 6px;
|
|
background-color: rgb(35, 30, 15);
|
|
border-width: 3px;
|
|
border-color: rgb(120, 100, 50);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
/* Build / Paint tab row. Shown only when the builder is selected; hidden
|
|
(display:none, set from HUDController) for tower/build-site selections. */
|
|
.command-tabs {
|
|
flex-direction: row;
|
|
flex-shrink: 0;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.cmd-tab {
|
|
flex: 1;
|
|
height: 22px;
|
|
font-size: 11px;
|
|
-unity-font-style: bold;
|
|
color: rgb(200, 190, 160);
|
|
background-color: rgb(26, 26, 10);
|
|
border-width: 1px;
|
|
border-color: rgb(58, 58, 26);
|
|
border-radius: 3px;
|
|
margin-right: 4px;
|
|
padding: 0;
|
|
}
|
|
|
|
.cmd-tab:hover {
|
|
background-color: rgb(42, 42, 21);
|
|
border-color: rgb(106, 106, 48);
|
|
}
|
|
|
|
.cmd-tab.active {
|
|
color: rgb(255, 224, 102);
|
|
background-color: rgb(42, 38, 16);
|
|
border-color: rgb(255, 224, 102);
|
|
}
|
|
|
|
/* Command grid: 3 row-containers injected at runtime, each holding 5 buttons. */
|
|
.command-grid {
|
|
flex: 1;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.cmd-row {
|
|
flex: 1;
|
|
flex-direction: row;
|
|
margin-bottom: 3px;
|
|
}
|
|
|
|
/* Button is the cell. The icon-placeholder fills the cell; hotkey and cost
|
|
labels are absolute-positioned overlays in the top-left and bottom-left
|
|
corners. (Name label was removed — name lives in the Tool Tip box on hover.) */
|
|
.cmd-btn {
|
|
flex: 1;
|
|
align-items: stretch;
|
|
justify-content: flex-start;
|
|
padding: 3px;
|
|
background-color: rgb(26, 26, 10);
|
|
border-width: 1px;
|
|
border-color: rgb(58, 58, 26);
|
|
border-radius: 3px;
|
|
margin-right: 3px;
|
|
}
|
|
|
|
/* Placeholder icon — flat grey square with embossed borders (light top/left,
|
|
dark bottom/right) to suggest a raised 3D button. Swap for background-image
|
|
when art exists. */
|
|
.cmd-icon-placeholder {
|
|
flex: 1;
|
|
background-color: rgb(140, 140, 140);
|
|
border-top-width: 2px;
|
|
border-left-width: 2px;
|
|
border-bottom-width: 2px;
|
|
border-right-width: 2px;
|
|
border-top-color: rgb(220, 220, 220);
|
|
border-left-color: rgb(220, 220, 220);
|
|
border-bottom-color: rgb(70, 70, 70);
|
|
border-right-color: rgb(70, 70, 70);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.cmd-btn:hover {
|
|
background-color: rgb(42, 42, 21);
|
|
border-color: rgb(106, 106, 48);
|
|
}
|
|
|
|
.cmd-btn.active {
|
|
background-color: rgb(42, 38, 16);
|
|
border-color: rgb(255, 224, 102);
|
|
}
|
|
|
|
.cmd-btn.unaffordable {
|
|
opacity: 0.4;
|
|
}
|
|
|
|
.cmd-btn.empty-slot {
|
|
opacity: 0.12;
|
|
}
|
|
|
|
/* Hotkey badge — top-left corner of the button, overlapping the icon. */
|
|
.cmd-hotkey {
|
|
position: absolute;
|
|
top: 2px;
|
|
left: 4px;
|
|
font-size: 11px;
|
|
color: rgb(255, 255, 255);
|
|
-unity-font-style: bold;
|
|
/* Drop-shadow-ish backdrop via semi-opaque text background for legibility. */
|
|
background-color: rgba(0, 0, 0, 0.55);
|
|
padding: 0 3px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* Cost badge — bottom-left corner of the icon. */
|
|
.cmd-cost {
|
|
position: absolute;
|
|
bottom: 2px;
|
|
left: 4px;
|
|
font-size: 10px;
|
|
color: rgb(255, 224, 102);
|
|
-unity-font-style: bold;
|
|
background-color: rgba(0, 0, 0, 0.55);
|
|
padding: 0 3px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* ----- Section 6: Spell Hotbar (short, hidden until the player has spells) ----- */
|
|
.spell-hotbar-frame {
|
|
width: 220px;
|
|
height: 165px;
|
|
flex-shrink: 0;
|
|
flex-direction: column;
|
|
padding: 8px 10px;
|
|
background-color: rgb(15, 15, 15);
|
|
border-width: 2px;
|
|
border-color: rgb(80, 65, 35);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.spell-hotbar {
|
|
flex-direction: row;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* One granted spell. Reuses the command-grid icon/badge look (embossed grey
|
|
placeholder + top-left hotkey badge) so spells read as the same visual
|
|
language as tower/build buttons. */
|
|
.spell-slot {
|
|
width: 48px;
|
|
height: 48px;
|
|
flex-shrink: 0;
|
|
margin-right: 4px;
|
|
position: relative;
|
|
}
|
|
|
|
.spell-slot-icon {
|
|
flex: 1;
|
|
background-color: rgb(140, 140, 140);
|
|
border-top-width: 2px;
|
|
border-left-width: 2px;
|
|
border-bottom-width: 2px;
|
|
border-right-width: 2px;
|
|
border-top-color: rgb(220, 220, 220);
|
|
border-left-color: rgb(220, 220, 220);
|
|
border-bottom-color: rgb(70, 70, 70);
|
|
border-right-color: rgb(70, 70, 70);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.spell-slot-hotkey {
|
|
position: absolute;
|
|
top: 2px;
|
|
left: 4px;
|
|
font-size: 11px;
|
|
color: rgb(255, 255, 255);
|
|
-unity-font-style: bold;
|
|
background-color: rgba(0, 0, 0, 0.55);
|
|
padding: 0 3px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* Remaining-cooldown seconds, bottom-left — same slot as .cmd-cost. Empty
|
|
string (no text) while the spell is ready. */
|
|
.spell-slot-cooldown-label {
|
|
position: absolute;
|
|
bottom: 2px;
|
|
left: 4px;
|
|
font-size: 10px;
|
|
color: rgb(255, 224, 102);
|
|
-unity-font-style: bold;
|
|
background-color: rgba(0, 0, 0, 0.55);
|
|
padding: 0 3px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
/* Whole-slot dim while on cooldown — same opacity convention as .cmd-btn.unaffordable. */
|
|
.spell-slot.on-cooldown {
|
|
opacity: 0.4;
|
|
}
|