Skip to content

Commit

Permalink
Add the Sneaky Dragon skill.
Browse files Browse the repository at this point in the history
GitOrigin-RevId: dab33b6e8ecf2b7e0a37692132ff1104c0f2277e
  • Loading branch information
cpojer committed Nov 1, 2024
1 parent d2bae9a commit bed88d7
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 27 deletions.
14 changes: 9 additions & 5 deletions apollo/actions/applyPower.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Flamethrower,
InfernoJetpack,
Pioneer,
UnitInfo,
Saboteur,
Zombie,
} from '@deities/athena/info/Unit.tsx';
import assignDeterministicUnitNames from '@deities/athena/lib/assignDeterministicUnitNames.tsx';
Expand All @@ -19,14 +19,18 @@ import updatePlayer from '@deities/athena/lib/updatePlayer.tsx';
import updatePlayers from '@deities/athena/lib/updatePlayers.tsx';
import { Charge, HealAmount } from '@deities/athena/map/Configuration.tsx';
import Player from '@deities/athena/map/Player.tsx';
import Unit from '@deities/athena/map/Unit.tsx';
import Unit, { UnitConversion } from '@deities/athena/map/Unit.tsx';
import Vector from '@deities/athena/map/Vector.tsx';
import MapData from '@deities/athena/MapData.tsx';
import { VisionT } from '@deities/athena/Vision.tsx';

const conversions = new Map<Skill, Readonly<{ from: UnitInfo; to: UnitInfo }>>([
const conversions = new Map<Skill, Readonly<UnitConversion>>([
[Skill.SpawnUnitInfernoJetpack, { from: Flamethrower, to: InfernoJetpack }],
[Skill.UnlockZombie, { from: Pioneer, to: Zombie }],
[
Skill.DragonSaboteur,
{ from: Saboteur, onlyLeader: true, recover: true, to: Dragon },
],
]);

const getAllOpponents = (
Expand Down Expand Up @@ -86,7 +90,7 @@ export function onPowerUnitUpgrade(skill: Skill, unit: Unit) {

const conversion = conversions.get(skill);
if (conversion) {
return unit.maybeConvert(conversion.from, conversion.to);
return unit.maybeConvert(conversion);
}

return null;
Expand Down Expand Up @@ -179,7 +183,7 @@ export default function applyPower(skill: Skill, map: MapData) {
if (conversion) {
const newUnits = map.units
.filter((unit) => map.matchesPlayer(player, unit))
.map((unit) => unit.maybeConvert(conversion.from, conversion.to));
.map((unit) => unit.maybeConvert(conversion));

map = map.copy({
units: map.units.merge(
Expand Down
82 changes: 82 additions & 0 deletions athena/info/Skill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import getAirUnitsToRecover from '../lib/getAirUnitsToRecover.tsx';
import { Charge } from '../map/Configuration.tsx';
import Entity, { EntityType, isUnit, isUnitInfo } from '../map/Entity.tsx';
import Player from '../map/Player.tsx';
import type Unit from '../map/Unit.tsx';
import Vector from '../map/Vector.tsx';
import type MapData from '../MapData.tsx';
import { BuildingInfo } from './Building.tsx';
Expand Down Expand Up @@ -52,6 +53,7 @@ export enum Skill {
VampireHeal = 37,
Shield = 38,
Charge = 39,
DragonSaboteur = 40,
}

export const Skills = new Set<Skill>([
Expand Down Expand Up @@ -87,6 +89,7 @@ export const Skills = new Set<Skill>([
Skill.UnitAbilitySniperImmediateAction,
Skill.UnitInfantryForestAttackAndDefenseIncrease,
Skill.UnitRailDefenseIncreasePowerAttackIncrease,
Skill.DragonSaboteur,
Skill.RecoverAirUnits,
Skill.Shield,
Skill.Charge,
Expand Down Expand Up @@ -258,6 +261,12 @@ const skillConfig: Record<
group: SkillGroup.Invasion,
requiresCrystal: true,
},
[Skill.DragonSaboteur]: {
activateOnInvasion: true,
charges: 3,
cost: 1000,
group: SkillGroup.Special,
},
};

export const CampaignOnlySkills = new Set(
Expand Down Expand Up @@ -321,6 +330,7 @@ const attackUnitStatusEffects = new Map<Skill, SkillUnitModifierMap>([
],
[Skill.Sabotage, new Map([[UnitID.Saboteur, 0.5]])],
[Skill.VampireHeal, new Map([[UnitID.Medic, 0.5]])],
[Skill.DragonSaboteur, new Map([[UnitID.Dragon, 0.1]])],
]);

const attackPowerStatusEffects: SkillMap = new Map([
Expand Down Expand Up @@ -507,6 +517,7 @@ const skillMovementTypeRadiusEffects = new Map<
new Map([[Skill.MovementIncreaseGroundUnitDefenseDecrease, 1]]),
],
[MovementTypes.HeavySoldier, new Map([[Skill.BuyUnitSuperAPU, 1]])],
[MovementTypes.AirInfantry, new Map([[Skill.DragonSaboteur, 1]])],
]);

const skillMovementTypeRadiusPowerEffects = new Map<
Expand Down Expand Up @@ -1157,6 +1168,19 @@ const getSkillActiveUnitTypes = (
return list;
}

if (skill === Skill.DragonSaboteur) {
for (const [vector, unit] of map.units) {
if (
unit.id === UnitID.Saboteur &&
unit.isLeader() &&
map.matchesPlayer(unit, player)
) {
list.push(vector);
}
}
return list;
}

if (skill === Skill.SpawnUnitInfernoJetpack) {
for (const [vector, unit] of map.units) {
if (unit.id === UnitID.Flamethrower) {
Expand Down Expand Up @@ -1275,3 +1299,61 @@ export function getSkillPowerDamage(skill: Skill) {
? vampirePowerDamage
: 0;
}

export function shouldUpgradeUnit(unit: Unit, skill: Skill) {
if (!unit.isCompleted()) {
return true;
}

switch (skill) {
case Skill.RecoverAirUnits:
case Skill.SpawnUnitInfernoJetpack:
case Skill.UnlockZombie:
case Skill.BuyUnitCannon:
case Skill.Shield:
case Skill.DragonSaboteur:
return true;

case Skill.BuyUnitAlien:
case Skill.BuyUnitBazookaBear:
case Skill.BuyUnitBear:
case Skill.BuyUnitOctopus:
case Skill.DecreaseUnitCostAttackAndDefenseDecreaseMinor:
case Skill.ArtilleryRangeIncrease:
case Skill.AttackAndDefenseDecreaseEasy:
case Skill.AttackAndDefenseIncreaseHard:
case Skill.AttackIncreaseMajorDefenseDecreaseMajor:
case Skill.AttackIncreaseMinor:
case Skill.BuyUnitAcidBomber:
case Skill.BuyUnitAIU:
case Skill.BuyUnitBrute:
case Skill.BuyUnitCommander:
case Skill.BuyUnitDinosaur:
case Skill.BuyUnitDragon:
case Skill.BuyUnitOgre:
case Skill.BuyUnitSuperAPU:
case Skill.BuyUnitSuperTank:
case Skill.BuyUnitZombieDefenseDecreaseMajor:
case Skill.Charge:
case Skill.CounterAttackPower:
case Skill.DefenseIncreaseMinor:
case Skill.HealInfantryMedicPower:
case Skill.HealVehiclesAttackDecrease:
case Skill.MovementIncreaseGroundUnitDefenseDecrease:
case Skill.NoUnitRestrictions:
case Skill.Sabotage:
case Skill.UnitAbilitySniperImmediateAction:
case Skill.UnitBattleShipMoveAndAct:
case Skill.UnitInfantryForestAttackAndDefenseIncrease:
case Skill.UnitRailDefenseIncreasePowerAttackIncrease:
case Skill.UnlockPowerStation:
case Skill.VampireHeal:
return false;
default: {
skill satisfies never;
throw new UnknownTypeError('shouldUpgradeUnit', String(skill));
}
}

return false;
}
10 changes: 7 additions & 3 deletions athena/lib/powerSpawnUnits.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
BazookaBear,
Bear,
InfernoJetpack,
Saboteur,
UnitInfo,
} from '../info/Unit.tsx';
import Building from '../map/Building.tsx';
Expand All @@ -28,25 +29,28 @@ const spawnConfiguration: Partial<Record<Skill, SpawnConfiguration>> = {
matchBuilding: (building) => building.id === Bar.id,
unitType: BazookaBear,
},

[Skill.BuyUnitBear]: {
matchBuilding: (building) =>
building.id === Shelter.id || building.id === HQ.id,
unitType: Bear,
},

[Skill.BuyUnitAlien]: {
matchBuilding: (building) =>
building.id === Barracks.id || building.id === HQ.id,
unitType: Alien,
},

[Skill.SpawnUnitInfernoJetpack]: {
matchBuilding: () => true,
matchUnit: () => true,
max: 3,
unitType: InfernoJetpack,
},
[Skill.DragonSaboteur]: {
matchBuilding: (building) => building.id === HQ.id,
matchUnit: () => true,
max: 1,
unitType: Saboteur,
},
};

const getDeployVector = (
Expand Down
35 changes: 25 additions & 10 deletions athena/map/Unit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import UnknownTypeError from '@deities/hephaestus/UnknownTypeError.tsx';
import { LowHealthZombieSkillConversion, Skill } from '../info/Skill.tsx';
import {
Ability,
Dragon,
getUnitInfo,
Saboteur,
Sniper,
Expand All @@ -27,6 +28,13 @@ export type PlainDryUnit =

type Ammo = ReadonlyMap<WeaponID, Supply>;

export type UnitConversion = Readonly<{
from: UnitInfo;
onlyLeader?: true;
recover?: true;
to: UnitInfo;
}>;

export enum UnitStatusEffect {
Poison = 1,
}
Expand Down Expand Up @@ -212,25 +220,28 @@ export class TransportedUnit {
return unit.isLeader() ? unit.copy({ name: null }) : unit;
}

maybeConvert(from: UnitInfo, to: UnitInfo): TransportedUnit {
maybeConvert(conversion: UnitConversion): TransportedUnit {
const { from, onlyLeader, recover, to } = conversion;
let unit =
from.id === this.id
from.id === this.id && (!onlyLeader || this.isLeader())
? to
.create(this.player, {
behavior: this.behavior,
label: this.label,
})
.setHealth(this.health)
.copy({
moved: this.moved ? true : undefined,
moved: !recover && this.moved ? true : undefined,
transports: this.transports,
})
.transport()
: this;

if (unit.transports?.length) {
unit = unit.copy({
transports: unit.transports.map((unit) => unit.maybeConvert(from, to)),
transports: unit.transports.map((unit) =>
unit.maybeConvert(conversion),
),
});
}

Expand Down Expand Up @@ -485,7 +496,8 @@ export default class Unit extends Entity {
(this.info === Sniper &&
this.isLeader() &&
!this.isUnfolded() &&
player.skills.has(Skill.UnitAbilitySniperImmediateAction))
player.skills.has(Skill.UnitAbilitySniperImmediateAction)) ||
(this.info === Dragon && player.skills.has(Skill.DragonSaboteur))
);
}

Expand Down Expand Up @@ -760,26 +772,29 @@ export default class Unit extends Entity {
return unit.isLeader() ? unit.copy({ name: null }) : unit;
}

maybeConvert(from: UnitInfo, to: UnitInfo): Unit {
maybeConvert(conversion: UnitConversion): Unit {
const { from, onlyLeader, recover, to } = conversion;
let unit =
from.id === this.id
from.id === this.id && (!onlyLeader || this.isLeader())
? to
.create(this.player, {
behavior: this.behavior,
label: this.label,
})
.setHealth(this.health)
.copy({
completed: this.isCompleted() ? true : undefined,
moved: this.hasMoved() ? true : undefined,
completed: !recover && this.isCompleted() ? true : undefined,
moved: !recover && this.hasMoved() ? true : undefined,
shield: this.shield,
transports: this.transports,
})
: this;

if (unit.transports?.length) {
unit = unit.copy({
transports: unit.transports.map((unit) => unit.maybeConvert(from, to)),
transports: unit.transports.map((unit) =>
unit.maybeConvert(conversion),
),
});
}

Expand Down
1 change: 1 addition & 0 deletions dionysus/lib/shouldActivatePower.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const shouldConsiderUnitRatio = (skill: Skill) => {
case Skill.Shield:
return false;

case Skill.DragonSaboteur:
case Skill.ArtilleryRangeIncrease:
case Skill.AttackAndDefenseDecreaseEasy:
case Skill.AttackAndDefenseIncreaseHard:
Expand Down
20 changes: 11 additions & 9 deletions hera/behavior/activatePower/activatePowerAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import {
getHealUnitTypes,
getSkillPowerDamage,
shouldUpgradeUnit,
Skill,
} from '@deities/athena/info/Skill.tsx';
import { HealEntry } from '@deities/athena/lib/getUnitsToHeal.tsx';
Expand Down Expand Up @@ -82,6 +83,9 @@ export default async function activatePowerAction(
.disableActiveSkills()
.activateSkill(skill),
),
units: actionResponse.units
? state.map.units.merge(actionResponse.units)
: state.map.units,
})
.getActiveUnitTypes()
.get(player.id);
Expand Down Expand Up @@ -120,17 +124,15 @@ export default async function activatePowerAction(
animateHeal(state, sortByVectorKey(unitsToHeal), next)
: null;

const unitsToUpgrade = state.map.units.filter(
const shouldUpgradeFirst = skill === Skill.SpawnUnitInfernoJetpack;
const unitsToUpgrade = (
shouldUpgradeFirst ? state.map : finalMap
).units.filter(
(unit, vector) =>
!healVectors.has(vector) &&
(!unit.isCompleted() ||
skill === Skill.RecoverAirUnits ||
skill === Skill.SpawnUnitInfernoJetpack ||
skill === Skill.UnlockZombie ||
skill === Skill.BuyUnitCannon ||
skill === Skill.Shield) &&
state.map.matchesPlayer(unit, player) &&
matchesActiveType(unitTypes, unit, vector),
matchesActiveType(unitTypes, unit, vector) &&
shouldUpgradeUnit(unit, skill),
);

const spawnUnits = unitsToSpawn?.size
Expand Down Expand Up @@ -162,7 +164,7 @@ export default async function activatePowerAction(
const queue = [
damage,
healUnits,
...(skill === Skill.SpawnUnitInfernoJetpack
...(shouldUpgradeFirst
? [upgrade, spawnUnits]
: [spawnUnits, upgrade]),
].filter(isPresent);
Expand Down
1 change: 1 addition & 0 deletions hera/lib/getSkillBasedPortrait.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default function getSkillBasedPortrait(skill: Skill): UnitInfo | null {
case Skill.BuyUnitBear:
return Bear;
case Skill.BuyUnitDragon:
case Skill.DragonSaboteur:
return Dragon;
case Skill.BuyUnitOgre:
return Ogre;
Expand Down
Loading

0 comments on commit bed88d7

Please sign in to comment.