From 0ac144b6510e383c48056c90309fdced35a75112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20=C5=BBegle=C5=84?= Date: Wed, 16 Jul 2025 19:07:12 +0200 Subject: [PATCH 1/4] Add experience collection --- core/archetypes/facility.ts | 1 + core/archetypes/ship.ts | 7 ++++++ core/components/component.ts | 4 +++ core/components/experience.ts | 21 ++++++++++++++++ core/components/hitpoints.ts | 16 +++++++++--- core/components/masks.ts | 2 ++ core/components/modifiers.ts | 35 +++++++++++++++++++++++++++ core/systems/attacking.ts | 4 +-- core/systems/deadUnregistering.ts | 30 +++++++++++++++++++++++ core/systems/hitpointsRegenerating.ts | 2 -- core/world/data/base.json | 4 +-- 11 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 core/components/experience.ts create mode 100644 core/components/modifiers.ts diff --git a/core/archetypes/facility.ts b/core/archetypes/facility.ts index ed465938..f5f40253 100644 --- a/core/archetypes/facility.ts +++ b/core/archetypes/facility.ts @@ -88,6 +88,7 @@ export function createFacility(sim: Sim, initial: InitialFacilityInput) { .addComponent({ name: "hitpoints", hp: { max: 100000, regen: 0, value: 100000 }, + hitBy: {}, }) .addComponent({ name: "name", diff --git a/core/archetypes/ship.ts b/core/archetypes/ship.ts index 3925fa57..069dde73 100644 --- a/core/archetypes/ship.ts +++ b/core/archetypes/ship.ts @@ -28,6 +28,7 @@ export const shipComponents = [ "journal", "model", "subordinates", + "experience", ] as const; export type ShipComponent = (typeof shipComponents)[number]; @@ -127,6 +128,7 @@ export function createShip(sim: Sim, initial: InitialShipInput): Ship { regen: initial.hitpoints.shield.regen, value: initial.hitpoints.shield.value, }, + hitBy: {}, }) .addComponent({ ...initial.damage, @@ -143,6 +145,11 @@ export function createShip(sim: Sim, initial: InitialShipInput): Ship { value: createShipName(entity.requireComponents(["model"])), }) .addComponent({ name: "subordinates", ids: [] }) + .addComponent({ + name: "experience", + rank: 1, + amount: 0, + }) .addTag("selection") .addTag("ship") .addTag(`role:${initial.role}`); diff --git a/core/components/component.ts b/core/components/component.ts index c4780cfa..27d7e06e 100644 --- a/core/components/component.ts +++ b/core/components/component.ts @@ -45,6 +45,8 @@ import type { CrewRequirement } from "./crewRequirement"; import type { Movable } from "./movable"; import type { StorageTransfer } from "./storageTransfer"; import type { Policies } from "./policies"; +import type { Experience } from "./experience"; +import type { Modifiers } from "./modifiers"; export interface CoreComponents { ai: Ai; @@ -67,6 +69,7 @@ export interface CoreComponents { dockable: Dockable; docks: Docks; drive: Drive; + experience: Experience; facilityModuleBonus: FacilityModuleBonus; facilityModuleQueue: FacilityModuleQueue; hecsPosition: HECSPosition; @@ -81,6 +84,7 @@ export interface CoreComponents { missions: Missions; model: Model; modules: Modules; + modifiers: Modifiers; movable: Movable; name: Name; orders: Orders; diff --git a/core/components/experience.ts b/core/components/experience.ts new file mode 100644 index 00000000..52b71c24 --- /dev/null +++ b/core/components/experience.ts @@ -0,0 +1,21 @@ +import type { RequireComponent } from "@core/tsHelpers"; +import type { BaseComponent } from "./component"; + +export interface Experience extends BaseComponent<"experience"> { + amount: number; + rank: number; +} + +const ranks = [200, 600, 1400, 3000, 6200]; + +function getRank(exp: number): number { + return ranks.findIndex((threshold) => exp < threshold) + 1; +} + +export function addExperience( + entity: RequireComponent<"experience">, + value: number +): void { + entity.cp.experience.amount += value; + entity.cp.experience.rank = getRank(entity.cp.experience.amount); +} diff --git a/core/components/hitpoints.ts b/core/components/hitpoints.ts index 99fa4ed3..892bc96e 100644 --- a/core/components/hitpoints.ts +++ b/core/components/hitpoints.ts @@ -12,10 +12,10 @@ export interface HitPoints extends BaseComponent<"hitpoints"> { value: number; regen: number; }; - hit?: boolean; + hitBy: Record; // entityId: timestamp } -export function changeHp( +export function subtractHp( entity: RequireComponent<"hitpoints">, value: number ): void { @@ -25,9 +25,17 @@ export function changeHp( 0, entity.cp.hitpoints.shield.value - delta ); - delta -= Math.min(entity.cp.hitpoints.shield.value, value); + delta += Math.min(entity.cp.hitpoints.shield.value, value); } entity.cp.hitpoints.hp.value -= delta; - entity.cp.hitpoints.hit = true; +} + +export function dealDamageToEntity( + entity: RequireComponent<"hitpoints">, + value: number, + attackerId: number +): void { + subtractHp(entity, -value); + entity.cp.hitpoints.hitBy[attackerId] = entity.sim.getTime(); } diff --git a/core/components/masks.ts b/core/components/masks.ts index f1f6101b..43aa8a14 100644 --- a/core/components/masks.ts +++ b/core/components/masks.ts @@ -55,6 +55,8 @@ export const componentList = [ "movable", "storageTransfer", "policies", + "experience", + "modifiers", ]; export const componentMask: Record = componentList.reduce( diff --git a/core/components/modifiers.ts b/core/components/modifiers.ts new file mode 100644 index 00000000..167c4046 --- /dev/null +++ b/core/components/modifiers.ts @@ -0,0 +1,35 @@ +import type { BaseComponent } from "./component"; + +export interface BaseModifier { + name: string; +} + +export interface SpeedModifier extends BaseModifier { + type: "speed"; + value: number; +} + +export interface DamageModifier extends BaseModifier { + type: "damage"; + value: number; +} + +export interface HitPointsModifier extends BaseModifier { + type: "hitpoints"; + value: number; +} + +export interface ShieldModifier extends BaseModifier { + type: "shield"; + value: number; +} + +export type Modifier = + | SpeedModifier + | DamageModifier + | HitPointsModifier + | ShieldModifier; + +export interface Modifiers extends BaseComponent<"modifiers"> { + modifiers: Modifier[]; +} diff --git a/core/systems/attacking.ts b/core/systems/attacking.ts index 93b8bb53..af0f8b55 100644 --- a/core/systems/attacking.ts +++ b/core/systems/attacking.ts @@ -1,4 +1,4 @@ -import { changeHp } from "@core/components/hitpoints"; +import { dealDamageToEntity } from "@core/components/hitpoints"; import settings from "@core/settings"; import type { Sim } from "@core/sim"; import type { RequireComponent } from "@core/tsHelpers"; @@ -142,7 +142,7 @@ export class AttackingSystem extends System { transport3D.hooks.shoot.notify( entity.requireComponents(["position", "damage"]) ); - changeHp(target, entity.cp.damage.value); + dealDamageToEntity(target, -entity.cp.damage.value, entity.id); const parentEntity = entityOrParent; if (target.hasComponents(["drive", "movable"])) { diff --git a/core/systems/deadUnregistering.ts b/core/systems/deadUnregistering.ts index 0f62b765..9ef8de94 100644 --- a/core/systems/deadUnregistering.ts +++ b/core/systems/deadUnregistering.ts @@ -2,9 +2,18 @@ import type { Sim } from "@core/sim"; import { dumpCargo } from "@core/components/storage"; import type { Faction } from "@core/archetypes/faction"; import { entityIndexer } from "@core/entityIndexer/entityIndexer"; +import type { DockSize } from "@core/components/dockable"; +import { addExperience } from "@core/components/experience"; import { System } from "./system"; import { transport3D } from "./transport3d"; +const expValues: Record = { + large: 200, + medium: 50, + small: 20, +}; +const timestampThreshold = 120; // 2 minutes + export class DeadUnregisteringSystem extends System { apply = (sim: Sim) => { super.apply(sim); @@ -27,8 +36,29 @@ export class DeadUnregisteringSystem extends System { time: this.sim.getTime(), }); } + if (entity.hasComponents(["position"])) transport3D.hooks.explode.notify(entity); + + const attackers: number[] = []; + + for (const [attackerId, timestamp] of Object.entries( + entity.cp.hitpoints.hitBy + )) { + if (timestamp + timestampThreshold > this.sim.getTime()) { + attackers.push(Number(attackerId)); + } + } + + const exp = + expValues[entity.cp.dockable?.size || "small"] / attackers.length; + for (const attackerId of attackers) { + const attacker = this.sim.get(attackerId); + if (attacker?.hasComponents(["experience"])) { + addExperience(attacker, exp); + } + } + entity.unregister("dead"); } } diff --git a/core/systems/hitpointsRegenerating.ts b/core/systems/hitpointsRegenerating.ts index cc95507a..f6ce8ae8 100644 --- a/core/systems/hitpointsRegenerating.ts +++ b/core/systems/hitpointsRegenerating.ts @@ -29,8 +29,6 @@ export class HitpointsRegeneratingSystem extends System<"exec"> { entity.cp.hitpoints.shield.max ); } - - entity.cp.hitpoints.hit = true; } }; } diff --git a/core/world/data/base.json b/core/world/data/base.json index dbafde4b..532527dc 100644 --- a/core/world/data/base.json +++ b/core/world/data/base.json @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cef32af0ebdff35f2bf2fb67bf0cf844705246fd122d3a837524dbaf7be9f829 -size 3379577 +oid sha256:889e6095bd08630042f7fe839629d6be2e61a6966d0c87621906367a4adbc58a +size 3383560 From b829ba4a98b90931305e96e53028a7b7bc73b92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20=C5=BBegle=C5=84?= Date: Thu, 17 Jul 2025 02:03:25 +0200 Subject: [PATCH 2/4] Add modifiers system --- core/archetypes/facility.ts | 2 +- core/archetypes/ship.ts | 7 ++++++ core/components/component.ts | 2 -- core/components/damage.ts | 15 +++++++++++- core/components/experience.ts | 14 ++++++++++- core/components/hitpoints.ts | 17 ++++++++++++- core/components/masks.ts | 1 - core/components/modifiers.ts | 35 --------------------------- core/sim/baseConfig.ts | 2 ++ core/systems/attacking.ts | 2 +- core/systems/modifierRecalculating.ts | 29 ++++++++++++++++++++++ core/tags.ts | 2 +- core/world/data/base.json | 4 +-- devtools/facilityModules/General.tsx | 4 +-- 14 files changed, 88 insertions(+), 48 deletions(-) delete mode 100644 core/components/modifiers.ts create mode 100644 core/systems/modifierRecalculating.ts diff --git a/core/archetypes/facility.ts b/core/archetypes/facility.ts index f5f40253..1fdee4ee 100644 --- a/core/archetypes/facility.ts +++ b/core/archetypes/facility.ts @@ -87,7 +87,7 @@ export function createFacility(sim: Sim, initial: InitialFacilityInput) { .addComponent({ name: "journal", entries: [] }) .addComponent({ name: "hitpoints", - hp: { max: 100000, regen: 0, value: 100000 }, + hp: { base: 100000, max: 100000, regen: 0, value: 100000, modifiers: {} }, hitBy: {}, }) .addComponent({ diff --git a/core/archetypes/ship.ts b/core/archetypes/ship.ts index 069dde73..cefd2e21 100644 --- a/core/archetypes/ship.ts +++ b/core/archetypes/ship.ts @@ -119,9 +119,11 @@ export function createShip(sim: Sim, initial: InitialShipInput): Ship { .addComponent({ name: "hitpoints", hp: { + base: initial.hitpoints.hp.value, max: initial.hitpoints.hp.value, regen: initial.hitpoints.hp.regen, value: initial.hitpoints.hp.value, + modifiers: {}, }, shield: { max: initial.hitpoints.shield.value, @@ -134,6 +136,11 @@ export function createShip(sim: Sim, initial: InitialShipInput): Ship { ...initial.damage, name: "damage", targetId: null, + output: { + base: initial.damage.value, + current: initial.damage.value, + }, + modifiers: {}, }) .addComponent({ name: "model", diff --git a/core/components/component.ts b/core/components/component.ts index 27d7e06e..1ea4e078 100644 --- a/core/components/component.ts +++ b/core/components/component.ts @@ -46,7 +46,6 @@ import type { Movable } from "./movable"; import type { StorageTransfer } from "./storageTransfer"; import type { Policies } from "./policies"; import type { Experience } from "./experience"; -import type { Modifiers } from "./modifiers"; export interface CoreComponents { ai: Ai; @@ -84,7 +83,6 @@ export interface CoreComponents { missions: Missions; model: Model; modules: Modules; - modifiers: Modifiers; movable: Movable; name: Name; orders: Orders; diff --git a/core/components/damage.ts b/core/components/damage.ts index 161ee640..1d98b7b4 100644 --- a/core/components/damage.ts +++ b/core/components/damage.ts @@ -4,6 +4,19 @@ export interface Damage extends BaseComponent<"damage"> { cooldown: number; targetId: number | null; range: number; - value: number; angle: number; + modifiers: Record; + output: { + base: number; + current: number; + }; +} + +export function recalculate(cp: Damage): void { + let multiplier = 1; + for (const mod of Object.values(cp.modifiers)) { + multiplier += mod; + } + + cp.output.current = cp.output.base * multiplier; } diff --git a/core/components/experience.ts b/core/components/experience.ts index 52b71c24..1472aa2b 100644 --- a/core/components/experience.ts +++ b/core/components/experience.ts @@ -17,5 +17,17 @@ export function addExperience( value: number ): void { entity.cp.experience.amount += value; - entity.cp.experience.rank = getRank(entity.cp.experience.amount); + const newRank = getRank(entity.cp.experience.amount); + if (newRank > entity.cp.experience.rank) { + entity.cp.experience.rank = newRank; + + if (entity.hasComponents(["damage"])) { + entity.cp.damage.modifiers.rank = 0.1 * (entity.cp.experience.rank - 1); + } + if (entity.hasComponents(["hitpoints"])) { + entity.cp.hitpoints.hp.modifiers.rank = + 0.1 * (entity.cp.experience.rank - 1); + } + entity.addTag("recalculate:modifiers"); + } } diff --git a/core/components/hitpoints.ts b/core/components/hitpoints.ts index 892bc96e..5c524561 100644 --- a/core/components/hitpoints.ts +++ b/core/components/hitpoints.ts @@ -3,9 +3,11 @@ import type { BaseComponent } from "./component"; export interface HitPoints extends BaseComponent<"hitpoints"> { hp: { + base: number; max: number; value: number; regen: number; + modifiers: Record; }; shield?: { max: number; @@ -36,6 +38,19 @@ export function dealDamageToEntity( value: number, attackerId: number ): void { - subtractHp(entity, -value); + subtractHp(entity, value); entity.cp.hitpoints.hitBy[attackerId] = entity.sim.getTime(); } + +export function recalculate(cp: HitPoints): void { + let multiplier = 1; + for (const mod of Object.values(cp.hp.modifiers)) { + multiplier += mod; + } + + const diff = cp.hp.base * multiplier - cp.hp.max; + cp.hp.max = cp.hp.base * multiplier; + if (multiplier > 1) { + cp.hp.value += diff; + } +} diff --git a/core/components/masks.ts b/core/components/masks.ts index 43aa8a14..d2828fed 100644 --- a/core/components/masks.ts +++ b/core/components/masks.ts @@ -56,7 +56,6 @@ export const componentList = [ "storageTransfer", "policies", "experience", - "modifiers", ]; export const componentMask: Record = componentList.reduce( diff --git a/core/components/modifiers.ts b/core/components/modifiers.ts deleted file mode 100644 index 167c4046..00000000 --- a/core/components/modifiers.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { BaseComponent } from "./component"; - -export interface BaseModifier { - name: string; -} - -export interface SpeedModifier extends BaseModifier { - type: "speed"; - value: number; -} - -export interface DamageModifier extends BaseModifier { - type: "damage"; - value: number; -} - -export interface HitPointsModifier extends BaseModifier { - type: "hitpoints"; - value: number; -} - -export interface ShieldModifier extends BaseModifier { - type: "shield"; - value: number; -} - -export type Modifier = - | SpeedModifier - | DamageModifier - | HitPointsModifier - | ShieldModifier; - -export interface Modifiers extends BaseComponent<"modifiers"> { - modifiers: Modifier[]; -} diff --git a/core/sim/baseConfig.ts b/core/sim/baseConfig.ts index f76cb8b1..7c635cb9 100644 --- a/core/sim/baseConfig.ts +++ b/core/sim/baseConfig.ts @@ -32,6 +32,7 @@ import { fogOfWarUpdatingSystem } from "@core/systems/fogOfWarUpdating"; import { storageTransferringSystem } from "@core/systems/storageTransferring"; import { sectorClaimingSystem } from "@core/systems/sectorClaiming"; import { NavigatingSystem } from "@core/systems/navigating"; +import { modifierRecalculatingSystem } from "@core/systems/modifierRecalculating"; import type { SimConfig } from "./Sim"; export const bootstrapSystems = [ @@ -56,6 +57,7 @@ export const bootstrapSystems = [ disposableUnregisteringSystem, crewGrowingSystem, storageTransferringSystem, + modifierRecalculatingSystem, ]; export const createBaseConfig = async (): Promise => { diff --git a/core/systems/attacking.ts b/core/systems/attacking.ts index af0f8b55..13697732 100644 --- a/core/systems/attacking.ts +++ b/core/systems/attacking.ts @@ -142,7 +142,7 @@ export class AttackingSystem extends System { transport3D.hooks.shoot.notify( entity.requireComponents(["position", "damage"]) ); - dealDamageToEntity(target, -entity.cp.damage.value, entity.id); + dealDamageToEntity(target, -entity.cp.damage.output.current, entity.id); const parentEntity = entityOrParent; if (target.hasComponents(["drive", "movable"])) { diff --git a/core/systems/modifierRecalculating.ts b/core/systems/modifierRecalculating.ts new file mode 100644 index 00000000..7de025c6 --- /dev/null +++ b/core/systems/modifierRecalculating.ts @@ -0,0 +1,29 @@ +import type { Sim } from "@core/sim"; +import { entityIndexer } from "@core/entityIndexer/entityIndexer"; +import { recalculate as recalculateDamage } from "@core/components/damage"; +import { recalculate as recalculateHitpoints } from "@core/components/hitpoints"; +import { System } from "./system"; + +export class ModifierRecalculatingSystem extends System { + apply(sim: Sim) { + sim.hooks.phase.start.subscribe( + this.constructor.name, + this.recalculateModifiers.bind(this) + ); + } + + // eslint-disable-next-line class-methods-use-this + recalculateModifiers(): void { + for (const entity of entityIndexer.search([], ["recalculate:modifiers"])) { + if (entity.hasComponents(["damage"])) { + recalculateDamage(entity.cp.damage); + } + if (entity.hasComponents(["hitpoints"])) { + recalculateHitpoints(entity.cp.hitpoints); + } + entity.removeTag("recalculate:modifiers"); + } + } +} + +export const modifierRecalculatingSystem = new ModifierRecalculatingSystem(); diff --git a/core/tags.ts b/core/tags.ts index 64e9503e..5ad59d6d 100644 --- a/core/tags.ts +++ b/core/tags.ts @@ -20,7 +20,7 @@ const tags = [ "ai:attack-force", "ai:spare", "ai:mission", - "ui:arrow", + "recalculate:modifiers", ...shipRoles.map<`role:${ShipRole}`>((role) => `role:${role}`), ...modules.map<`facilityModuleType:${FacilityModuleType}`>( ({ type: facilityModuleType }) => diff --git a/core/world/data/base.json b/core/world/data/base.json index 532527dc..a5b119ca 100644 --- a/core/world/data/base.json +++ b/core/world/data/base.json @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:889e6095bd08630042f7fe839629d6be2e61a6966d0c87621906367a4adbc58a -size 3383560 +oid sha256:3506873d04bc329048f96b7f7027d5cf2e1d39d33adc29e9b17fe7f8ad2366ca +size 3468373 diff --git a/devtools/facilityModules/General.tsx b/devtools/facilityModules/General.tsx index 35087cbd..b940c1fd 100644 --- a/devtools/facilityModules/General.tsx +++ b/devtools/facilityModules/General.tsx @@ -71,10 +71,10 @@ const FacilityModuleGeneralEditor: React.FC<{ index: number }> = ({ {facilityModule.type === "military" && ( )} From 121f9f6c29cff5d1dc6673c9cebe96bfac21b761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20=C5=BBegle=C5=84?= Date: Thu, 17 Jul 2025 02:10:00 +0200 Subject: [PATCH 3/4] Fix rank numbers --- core/components/experience.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/core/components/experience.ts b/core/components/experience.ts index 1472aa2b..7ccd64e1 100644 --- a/core/components/experience.ts +++ b/core/components/experience.ts @@ -6,10 +6,10 @@ export interface Experience extends BaseComponent<"experience"> { rank: number; } -const ranks = [200, 600, 1400, 3000, 6200]; +const ranks = [200, 600, 1400, 3000, 6200, Infinity]; function getRank(exp: number): number { - return ranks.findIndex((threshold) => exp < threshold) + 1; + return ranks.findIndex((threshold) => exp < threshold); } export function addExperience( @@ -22,11 +22,10 @@ export function addExperience( entity.cp.experience.rank = newRank; if (entity.hasComponents(["damage"])) { - entity.cp.damage.modifiers.rank = 0.1 * (entity.cp.experience.rank - 1); + entity.cp.damage.modifiers.rank = 0.1 * entity.cp.experience.rank; } if (entity.hasComponents(["hitpoints"])) { - entity.cp.hitpoints.hp.modifiers.rank = - 0.1 * (entity.cp.experience.rank - 1); + entity.cp.hitpoints.hp.modifiers.rank = 0.1 * entity.cp.experience.rank; } entity.addTag("recalculate:modifiers"); } From 1f6940253e6e71679f538d02117c3cd8961a7447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20=C5=BBegle=C5=84?= Date: Fri, 18 Jul 2025 01:31:22 +0200 Subject: [PATCH 4/4] Add experience to ui --- core/components/experience.ts | 4 +-- core/systems/attacking.ts | 2 +- ui/components/ExperienceBar/ExperienceBar.tsx | 26 +++++++++++++++ ui/components/ExperienceBar/styles.scss | 32 +++++++++++++++++++ ui/components/ExperienceBar/styles.scss.d.ts | 5 +++ ui/components/HitPoints/HitPoints.tsx | 11 +++---- ui/components/Panel/Panel.scss | 9 ++++++ ui/components/Panel/Panel.scss.d.ts | 1 + ui/components/Panel/Panel.tsx | 8 ++++- 9 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 ui/components/ExperienceBar/ExperienceBar.tsx create mode 100644 ui/components/ExperienceBar/styles.scss create mode 100644 ui/components/ExperienceBar/styles.scss.d.ts diff --git a/core/components/experience.ts b/core/components/experience.ts index 7ccd64e1..1ee9ba87 100644 --- a/core/components/experience.ts +++ b/core/components/experience.ts @@ -6,9 +6,9 @@ export interface Experience extends BaseComponent<"experience"> { rank: number; } -const ranks = [200, 600, 1400, 3000, 6200, Infinity]; +export const ranks = [200, 600, 1400, 3000, 6200, Infinity]; -function getRank(exp: number): number { +export function getRank(exp: number): number { return ranks.findIndex((threshold) => exp < threshold); } diff --git a/core/systems/attacking.ts b/core/systems/attacking.ts index 13697732..01766c04 100644 --- a/core/systems/attacking.ts +++ b/core/systems/attacking.ts @@ -142,7 +142,7 @@ export class AttackingSystem extends System { transport3D.hooks.shoot.notify( entity.requireComponents(["position", "damage"]) ); - dealDamageToEntity(target, -entity.cp.damage.output.current, entity.id); + dealDamageToEntity(target, entity.cp.damage.output.current, entity.id); const parentEntity = entityOrParent; if (target.hasComponents(["drive", "movable"])) { diff --git a/ui/components/ExperienceBar/ExperienceBar.tsx b/ui/components/ExperienceBar/ExperienceBar.tsx new file mode 100644 index 00000000..ef2aed87 --- /dev/null +++ b/ui/components/ExperienceBar/ExperienceBar.tsx @@ -0,0 +1,26 @@ +import { getRank, ranks } from "@core/components/experience"; +import React from "react"; +import styles from "./styles.scss"; + +export interface ExperienceBarProps { + amount: number; +} + +export const ExperienceBar: React.FC = ({ amount }) => { + const rank = getRank(amount); + const progress = rank === 5 ? 0 : amount / ranks[rank]; + const expLabel = rank === 5 ? "Max Rank" : `${amount}/${ranks[rank]}`; + const rankLabel = rank ? `Rank ${rank}` : "No Rank"; + + return ( + <> +
{rankLabel}
+
+ {expLabel} +
+ + ); +}; diff --git a/ui/components/ExperienceBar/styles.scss b/ui/components/ExperienceBar/styles.scss new file mode 100644 index 00000000..b305489f --- /dev/null +++ b/ui/components/ExperienceBar/styles.scss @@ -0,0 +1,32 @@ +.root { + --height: usesize(1.8); + --percent: 0%; + + height: var(--height); + border: usesize(0.1) solid var(--palette-border); + border-radius: usesize(0.2); + position: relative; + flex: 1; + + &::before { + content: ""; + display: block; + height: 100%; + width: var(--percent); + background-color: var(--palette-primary); + position: absolute; + top: 0; + left: 0; + border-radius: usesize(0.2); + } +} + +.label { + position: absolute; + display: inline-block; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + mix-blend-mode: difference; + font-size: var(--typography-label); +} diff --git a/ui/components/ExperienceBar/styles.scss.d.ts b/ui/components/ExperienceBar/styles.scss.d.ts new file mode 100644 index 00000000..666162f5 --- /dev/null +++ b/ui/components/ExperienceBar/styles.scss.d.ts @@ -0,0 +1,5 @@ +/* @generated */ +/* prettier-ignore */ +/* eslint-disable */ +export const root: string; +export const label: string; diff --git a/ui/components/HitPoints/HitPoints.tsx b/ui/components/HitPoints/HitPoints.tsx index 8e99fa1c..869735e6 100644 --- a/ui/components/HitPoints/HitPoints.tsx +++ b/ui/components/HitPoints/HitPoints.tsx @@ -31,12 +31,9 @@ export const HitPointsInfo: React.FC = ({ hp }) => { ); return ( - <> -
- {!!hp.shield && Shield} - Health -
-
- +
+ {!!hp.shield && Shield} + Health +
); }; diff --git a/ui/components/Panel/Panel.scss b/ui/components/Panel/Panel.scss index d312f9e6..8521df58 100644 --- a/ui/components/Panel/Panel.scss +++ b/ui/components/Panel/Panel.scss @@ -52,3 +52,12 @@ .turbo { color: var(--palette-error); } + +.hpBar { + display: flex; + align-items: center; + gap: usesize(0.8); + padding-bottom: usesize(0.8); + margin-bottom: usesize(0.8); + border-bottom: usesize(0.1) solid var(--palette-border); +} diff --git a/ui/components/Panel/Panel.scss.d.ts b/ui/components/Panel/Panel.scss.d.ts index aa7e59d3..e6e66a6e 100644 --- a/ui/components/Panel/Panel.scss.d.ts +++ b/ui/components/Panel/Panel.scss.d.ts @@ -11,3 +11,4 @@ export const spacer: string; export const tab: string; export const manage: string; export const turbo: string; +export const hpBar: string; diff --git a/ui/components/Panel/Panel.tsx b/ui/components/Panel/Panel.tsx index 073f3fe9..33525378 100644 --- a/ui/components/Panel/Panel.tsx +++ b/ui/components/Panel/Panel.tsx @@ -42,6 +42,7 @@ import { Docks } from "../Docks"; import ShipBuildingQueue from "../ShipBuildingQueue"; import { Production } from "../Production"; import { Teleport } from "../Teleport/Teleport"; +import { ExperienceBar } from "../ExperienceBar/ExperienceBar"; export interface PanelProps { expanded?: boolean; @@ -196,7 +197,12 @@ export const Panel: React.FC = ({ entity, expanded }) => { )} {entity.hasComponents(["hitpoints"]) && ( - +
+ + {entity.hasComponents(["experience"]) && showSensitive && ( + + )} +
)} {entity.hasComponents(shipComponents) && (