@zerospacegg/iolin
Version:
Pure TypeScript implementation of ZeroSpace game data processing (PKL-free)
368 lines • 13.4 kB
JavaScript
"use strict";
/**
* Unit - RTS unit class for ZeroSpace units
* Ported from unit.pkl with functional constructor pattern
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MobileMercOutpostUnit = exports.MercTopbarUnit = exports.MercUnit = exports.UltimateUnit = exports.SpecialUnit = exports.BuilderUnit = exports.HarvesterUnit = exports.CoopCommanderUnit = exports.HeroUnit = exports.ArmyUnit = exports.Unit = exports.ReanimateState = exports.InfuseState = void 0;
const entity_js_1 = require("./entity.cjs");
exports.InfuseState = {
none: 0,
single: 1,
double: 2,
};
exports.ReanimateState = {
none: 0,
alive: 1,
zombie: 2,
};
/**
* RTS Unit class base - extends Entity with unit-specific properties
* Usage: new ArmyUnit("Stinger", (unit) => { unit.hp = 100; })
*/
class Unit extends entity_js_1.Entity {
get type() {
return "unit";
}
// Ugly accessor methods for transformation state
_getInfused() {
return this._infused;
}
_setInfused(value) {
this._infused = value;
}
_getReanimated() {
return this._reanimated;
}
_setReanimated(value) {
this._reanimated = value;
}
constructor(props) {
super(props);
// Transformation state using private fields with ugly accessor methods
this._infused = exports.InfuseState.none;
this._reanimated = exports.ReanimateState.none;
this.vision = 1600; // Default vision for all units
this.pushability = 1; // Default pushability for all units - determines pathing priority
// Add unit-specific dynamic tags to avoid circular dependency
this.dynamicTaggers.push([(e) => e instanceof Unit && e.canBeInfused, "can-be-infused"], [(e) => e instanceof Unit && e.canBeReanimated, "can-be-reanimated"], [(e) => e instanceof Unit && e.canBeMindControlled, "can-be-mind-controlled"],
// Transformation state tags
[(e) => e instanceof Unit && e._getInfused() >= exports.InfuseState.single, "infused"], [
(e) => e instanceof Unit && e._getInfused() === exports.InfuseState.double,
"double-infused",
], [
(e) => e instanceof Unit && e._getReanimated() === exports.ReanimateState.alive,
"reanimated:alive",
], [
(e) => e instanceof Unit && e._getReanimated() === exports.ReanimateState.zombie,
"reanimated:zombie",
],
// Unit type tags from internal dev data
[
(e) => (e instanceof Unit && e.internalTags?.includes("Attackable.Biological")) ?? false,
"unit:bio",
], [
(e) => (e instanceof Unit && e.internalSecondaryTags?.includes("Mech")) ?? false,
"unit:mech",
], [
(e) => (e instanceof Unit && e.internalSecondaryTags?.includes("Vehicle")) ?? false,
"unit:vehicle",
], [
(e) => (e instanceof Unit && e.internalSecondaryTags?.includes("Infantry")) ?? false,
"unit:infantry",
]);
}
get subtype() {
return this.unitType;
}
// Computed properties from PKL
get canBeInfused() {
if (this.canBeInfusedOverride !== undefined) {
return this.canBeInfusedOverride;
}
// Can't infuse beyond double infusion
if (this._infused >= exports.InfuseState.double) {
return false;
}
// Check static tags only to avoid circular dependency
return (this.unitType === "army" || this.unitType === "merc") && !this.manualTags.includes("massive");
}
get canBeReanimated() {
return this.unitType === "army" || this.unitType === "merc" || this.unitType === "hero";
}
get canBeMindControlled() {
return this.unitType !== "hero";
}
// Cost calculation as getters from PKL
get reanimateCost() {
if (!this.canBeReanimated) {
return undefined;
}
const h = this.hexiteCost ?? 0;
const f = (this.fluxCost ?? 0) * 1.25;
const n = (h + f) * 0.0675;
return Math.round(n * 10) / 10;
}
get infuseCost() {
if (!this.canBeInfused) {
return undefined;
}
const s = this.supply ?? 0;
return Math.floor(s * 2);
}
/** Get all child entity IDs for ecosystem linking - includes upgrades */
get children() {
const children = super.children;
// Add all upgrade IDs with their navigation paths
for (const [key, upgrade] of Object.entries(this.upgrades)) {
if (upgrade.id)
children[upgrade.id] = ["upgrades", key];
}
return children;
}
/**
* JSON.stringify() calls this automatically
*/
toJSON() {
return {
...super.toJSON(),
unitType: this.unitType,
canBeInfused: this.canBeInfused,
canBeReanimated: this.canBeReanimated,
canBeMindControlled: this.canBeMindControlled,
reanimateCost: this.reanimateCost,
infuseCost: this.infuseCost,
};
}
// Ugly transformation methods that actually work
_applyInfusion(value) {
if (value !== this._infused) {
if (value === exports.InfuseState.none) {
// Reset to base stats
const base = new this.constructor();
this.hp = base.hp;
// Reset attack damage
for (const [key, attack] of Object.entries(this.attacks)) {
if (base.attacks[key]?.damage) {
attack.damage = base.attacks[key].damage;
}
}
}
else {
// Apply infusion multipliers
const multiplier = value === exports.InfuseState.double ? 2.25 : 1.5; // 1.5x for single, 2.25x for double
// Apply to HP
if (this._infused === exports.InfuseState.none) {
this.hp = Math.floor((this.hp ?? 0) * multiplier);
}
else {
// Transitioning from single to double
this.hp = Math.floor((this.hp ?? 0) * 1.5); // 1.5 * 1.5 = 2.25
}
// Apply to attack damage
const damageMultiplier = value === exports.InfuseState.double ? 2.25 : 1.5;
for (const attack of Object.values(this.attacks)) {
if (attack.damage) {
if (this._infused === exports.InfuseState.none) {
attack.damage = Math.floor(attack.damage * damageMultiplier);
}
else {
// Transitioning from single to double
attack.damage = Math.floor(attack.damage * 1.5);
}
}
}
}
this._infused = value;
}
}
_applyReanimation(value) {
if (value !== this._reanimated) {
if (value === exports.ReanimateState.none) {
// Reset to base stats
const base = new this.constructor();
this.hp = base.hp;
this.speed = base.speed;
// Reset attack speed
for (const [key, attack] of Object.entries(this.attacks)) {
if (base.attacks[key]?.cooldown) {
attack.cooldown = base.attacks[key].cooldown;
}
}
}
else {
// Apply reanimation effects
const hpMultiplier = value === exports.ReanimateState.zombie ? 0.7 : 1.3; // Zombie penalty or alive bonus
const speedMultiplier = value === exports.ReanimateState.zombie ? 0.7 : 1.3;
const attackSpeedMultiplier = value === exports.ReanimateState.zombie ? 0.7 : 1.3;
// Apply to HP and speed
if (this._reanimated === exports.ReanimateState.none) {
this.hp = Math.floor((this.hp ?? 0) * hpMultiplier);
this.speed = Math.floor((this.speed ?? 0) * speedMultiplier);
}
// Apply to attack speed (reduce cooldown for faster attacks)
for (const attack of Object.values(this.attacks)) {
if (attack.cooldown) {
if (this._reanimated === exports.ReanimateState.none) {
attack.cooldown = attack.cooldown / attackSpeedMultiplier;
}
}
}
}
this._reanimated = value;
}
}
/**
* Infuse this unit (chainable method)
* No-ops if unit is not infusable
*/
infuse(doubleInfuse = false) {
if (!this.canBeInfused) {
return this; // No-op if not infusable
}
this._applyInfusion(doubleInfuse ? exports.InfuseState.double : exports.InfuseState.single);
return this;
}
/**
* Reanimate this unit (chainable method)
* No-ops if unit is not reanimatable
*/
reanimate(zombieMode = false) {
if (!this.canBeReanimated) {
return this; // No-op if not reanimatable
}
if (zombieMode) {
this._applyInfusion(exports.InfuseState.none); // Zombie loses infusion first
this._applyReanimation(exports.ReanimateState.zombie); // Then becomes zombie
}
else {
this._applyReanimation(exports.ReanimateState.alive); // Alive reanimation
}
return this;
}
}
exports.Unit = Unit;
class ArmyUnit extends Unit {
constructor(props) {
super(props);
}
get unitType() {
return "army";
}
}
exports.ArmyUnit = ArmyUnit;
class HeroUnit extends Unit {
constructor() {
super(...arguments);
this.supply = 10; // All heroes cost 10 supply
this.buildTime = 20; // All heroes take 20 seconds to build
this.rebuildable = true; // All heroes can be rebuilt when they die
this.rebuildTime = 60; // All heroes take 60 seconds to rebuild
}
get unitType() {
return "hero";
}
}
exports.HeroUnit = HeroUnit;
class CoopCommanderUnit extends Unit {
get unitType() {
return "coop-commander";
}
constructor() {
super();
this.tier = "T2"; // All coop commanders are T2 like heroes
// Commander progression system
this.maxLevel = 15; // Default max level for commanders
this.currentLevel = 1; // Starting level
this.commanderTopbars = {};
this.levels = {};
}
// Auto-populate children with levels and topbars - kawaii style! (◕‿◕)♡
get children() {
const children = super.children;
// Add all commander levels
for (const [key, level] of Object.entries(this.levels)) {
if (level && level.id)
children[level.id] = ["levels", key];
}
// Add all topbar abilities
for (const [key, topbar] of Object.entries(this.commanderTopbars)) {
if (topbar && topbar.id)
children[topbar.id] = ["commanderTopbars", key];
}
return children;
}
/** Get topbars available at current level */
getAvailableTopbars() {
if (!this.commanderTopbars) {
this.commanderTopbars = {};
}
const available = {};
for (const [name, topbar] of Object.entries(this.commanderTopbars)) {
if (!topbar.requiredLevel || topbar.requiredLevel <= this.currentLevel) {
available[name] = topbar;
}
}
return available;
}
/** Set commander level (affects available topbars) - chainable! */
withLevel(level) {
this.currentLevel = Math.min(Math.max(1, level), this.maxLevel);
return this;
}
toJSON() {
return {
...super.toJSON(),
commanderType: this.commanderType,
commanderFaction: this.commanderFaction,
commanderTopbars: this.commanderTopbars,
levels: this.levels,
maxLevel: this.maxLevel,
currentLevel: this.currentLevel,
};
}
}
exports.CoopCommanderUnit = CoopCommanderUnit;
class HarvesterUnit extends Unit {
get unitType() {
return "harvester";
}
}
exports.HarvesterUnit = HarvesterUnit;
class BuilderUnit extends Unit {
get unitType() {
return "builder";
}
}
exports.BuilderUnit = BuilderUnit;
class SpecialUnit extends Unit {
get unitType() {
return "special";
}
}
exports.SpecialUnit = SpecialUnit;
class UltimateUnit extends Unit {
get unitType() {
return "ultimate";
}
}
exports.UltimateUnit = UltimateUnit;
class MercUnit extends Unit {
get unitType() {
return "merc";
}
}
exports.MercUnit = MercUnit;
class MercTopbarUnit extends Unit {
get unitType() {
return "merc-topbar";
}
}
exports.MercTopbarUnit = MercTopbarUnit;
class MobileMercOutpostUnit extends Unit {
get unitType() {
return "mobile-merc-outpost";
}
}
exports.MobileMercOutpostUnit = MobileMercOutpostUnit;
//# sourceMappingURL=unit.js.map