diff --git a/TODO.md b/TODO.md index efa401b..ee3d8fe 100644 --- a/TODO.md +++ b/TODO.md @@ -75,7 +75,6 @@ Ships models and actions * Add damage on collisions (when two ships are moved to the same place) * Add hull points to drones and make them take area damage * Allow to customize effects based on whether a target is enemy, allied or self -* Add anchored effect (cannot be moved) * Add a reflect damage effect * Add untargettable effect (can only be targetted with area effects) * Add damage modifier (to change the options of incoming damage or outgoing damage) diff --git a/data/stage2/image/action/pin.png b/data/stage2/image/action/pin.png new file mode 100644 index 0000000..4b6b1fc Binary files /dev/null and b/data/stage2/image/action/pin.png differ diff --git a/data/stage2/image/action/shieldbash.png b/data/stage2/image/action/shieldbash.png new file mode 100644 index 0000000..e7ba0f4 Binary files /dev/null and b/data/stage2/image/action/shieldbash.png differ diff --git a/graphics/ui/actions.svg b/graphics/ui/actions.svg index 1ed1d3e..db05a1c 100644 --- a/graphics/ui/actions.svg +++ b/graphics/ui/actions.svg @@ -16,7 +16,7 @@ version="1.1" inkscape:version="0.92.3 (3ce5693, 2018-03-11)" sodipodi:docname="actions.svg" - inkscape:export-filename="/home/michael/workspace/spacetac/data/stage2/image/action/endturn.png" + inkscape:export-filename="/home/michael/workspace/spacetac/data/stage2/image/action/pin.png" inkscape:export-xdpi="96.000008" inkscape:export-ydpi="96.000008" viewBox="0 0 256 256" @@ -1543,6 +1543,44 @@ y1="203.97403" x2="185.40526" y2="132.19669" /> + + + + + + + + + + inkscape:object-paths="true" + inkscape:snap-center="true"> + style="display:none"> @@ -1995,8 +2034,8 @@ @@ -2045,8 +2084,8 @@ @@ -2095,8 +2134,8 @@ @@ -2164,8 +2203,8 @@ @@ -2214,8 +2253,8 @@ @@ -2264,8 +2303,8 @@ @@ -2333,8 +2372,8 @@ @@ -2383,8 +2422,8 @@ @@ -2433,8 +2472,8 @@ @@ -2483,8 +2522,8 @@ @@ -2533,8 +2572,8 @@ @@ -2583,8 +2622,8 @@ @@ -2633,8 +2672,8 @@ @@ -2683,8 +2722,8 @@ @@ -2975,90 +3014,116 @@ + diff --git a/src/core/Ship.spec.ts b/src/core/Ship.spec.ts index 2c037eb..6315976 100644 --- a/src/core/Ship.spec.ts +++ b/src/core/Ship.spec.ts @@ -136,15 +136,15 @@ module TK.SpaceTac.Specs { test.case("lists active effects", check => { let ship = new Ship(); - check.equals(imaterialize(ship.ieffects()), []); + check.equals(ship.getEffects(), []); let effect1 = new AttributeEffect("evasion", 4); check.patch(ship.model, "getEffects", () => [effect1]); - check.equals(imaterialize(ship.ieffects()), [effect1]); + check.equals(ship.getEffects(), [effect1]); let effect2 = new AttributeLimitEffect("evasion", 2); ship.active_effects.add(new StickyEffect(effect2, 4)); - check.equals(imaterialize(ship.ieffects()), [effect1, effect2]); + check.equals(ship.getEffects(), [effect1, effect2]); }); test.case("gets a textual description of an attribute", check => { diff --git a/src/core/Ship.ts b/src/core/Ship.ts index dbd335c..f2b1687 100644 --- a/src/core/Ship.ts +++ b/src/core/Ship.ts @@ -331,7 +331,7 @@ module TK.SpaceTac { keys(this.attributes).forEach(attr => this.attributes[attr].reset()); // Apply attribute effects - iforeach(this.ieffects(), effect => { + this.getEffects().forEach(effect => { if (effect instanceof AttributeEffect) { this.attributes[effect.attrcode].addModifier(effect.value); } else if (effect instanceof AttributeMultiplyEffect) { @@ -371,10 +371,9 @@ module TK.SpaceTac { * * This combines the permanent effects from ship model, with sticky and area effects. */ - ieffects(): Iterator { - return ichain( - iarray(this.getModelEffects()), - imap(this.active_effects.iterator(), effect => (effect instanceof StickyEffect) ? effect.base : effect) + getEffects(): BaseEffect[] { + return this.getModelEffects().concat( + this.active_effects.list().map(effect => (effect instanceof StickyEffect) ? effect.base : effect) ); } diff --git a/src/core/actions/BaseAction.ts b/src/core/actions/BaseAction.ts index 35552da..68c17fa 100644 --- a/src/core/actions/BaseAction.ts +++ b/src/core/actions/BaseAction.ts @@ -48,7 +48,9 @@ module TK.SpaceTac { // Action is overheated OVERHEATED = "Overheated", // Vigilance is activated - VIGILANCE = "In vigilance" + VIGILANCE = "In vigilance", + // Ship is pinned + PINNED = "Pinned", } /** diff --git a/src/core/actions/MoveAction.ts b/src/core/actions/MoveAction.ts index d3145eb..5598041 100644 --- a/src/core/actions/MoveAction.ts +++ b/src/core/actions/MoveAction.ts @@ -67,6 +67,11 @@ module TK.SpaceTac { return ActionUnavailability.VIGILANCE; } + // Check pinned status + if (any(ship.getEffects(), effect => effect instanceof PinnedEffect)) { + return ActionUnavailability.PINNED; + } + return null; } diff --git a/src/core/effects/CooldownEffect.spec.ts b/src/core/effects/CooldownEffect.spec.ts index 6377e31..f2a5e61 100644 --- a/src/core/effects/CooldownEffect.spec.ts +++ b/src/core/effects/CooldownEffect.spec.ts @@ -30,7 +30,7 @@ module TK.SpaceTac { }) test.case("builds a textual description", check => { - check.equals(new CooldownEffect(0, 0).getDescription(), "Full cooling (all equipments)"); + check.equals(new CooldownEffect(0, 0).getDescription(), "full cooling (all equipments)"); check.equals(new CooldownEffect(1, 1).getDescription(), "1 cooling (1 equipment)"); check.equals(new CooldownEffect(2, 2).getDescription(), "2 cooling (2 equipments)"); }) diff --git a/src/core/effects/CooldownEffect.ts b/src/core/effects/CooldownEffect.ts index 2e4f3cd..2f525c1 100644 --- a/src/core/effects/CooldownEffect.ts +++ b/src/core/effects/CooldownEffect.ts @@ -34,7 +34,7 @@ module TK.SpaceTac { } getDescription(): string { - return `${this.cooling ? this.cooling : "Full"} cooling (${this.maxcount ? this.maxcount : "all"} equipment${this.maxcount != 1 ? "s" : ""})`; + return `${this.cooling ? this.cooling : "full"} cooling (${this.maxcount ? this.maxcount : "all"} equipment${this.maxcount != 1 ? "s" : ""})`; } } } diff --git a/src/core/effects/PinnedEffect.spec.ts b/src/core/effects/PinnedEffect.spec.ts new file mode 100644 index 0000000..7b36c8f --- /dev/null +++ b/src/core/effects/PinnedEffect.spec.ts @@ -0,0 +1,62 @@ +module TK.SpaceTac.Specs { + testing("PinnedEffect", test => { + test.case("shows a textual description", check => { + check.equals(new PinnedEffect().getDescription(), "pinned"); + check.equals(new PinnedEffect(true).getDescription(), "anchored"); + }); + + test.case("prevents a ship from using its engine", check => { + let ship = new Ship(); + TestTools.setShipModel(ship, 1, 1, 1); + + let engine = TestTools.addEngine(ship, 100); + check.equals(engine.checkCannotBeApplied(ship), null, "engine can initially be used"); + + let cases: [string, BaseEffect][] = [ + ["soft pin", new PinnedEffect()], + ["hard pin", new PinnedEffect(true)], + ["sticky soft pin", new StickyEffect(new PinnedEffect())], + ["sticky hard pin", new StickyEffect(new PinnedEffect(true))], + ]; + cases.forEach(([title, effect]) => { + check.in(title, check => { + ship.active_effects.add(effect); + check.equals(engine.checkCannotBeApplied(ship), ActionUnavailability.PINNED, "engine cannot be used when pinned"); + ship.active_effects.remove(effect); + check.equals(engine.checkCannotBeApplied(ship), null, "engine can be used again"); + }); + }); + }); + + test.case("prevents a ship from being moved by another effect, in hard mode", check => { + let battle = TestTools.createBattle(); + let ship = battle.fleets[0].ships[0]; + let enemy = battle.fleets[1].ships[0]; + enemy.setArenaPosition(0, 500); + + let effect = new RepelEffect(100); + check.equals(effect.getOnDiffs(enemy, ship).length, 1, "ship can initially be moved"); + + check.in("soft pin", check => { + let pin = enemy.active_effects.add(new PinnedEffect()); + check.equals(effect.getOnDiffs(enemy, ship).length, 1, "ship can still be moved"); + enemy.active_effects.remove(pin); + check.equals(effect.getOnDiffs(enemy, ship).length, 1, "ship can still be moved"); + }); + + check.in("hard pin", check => { + let pin = enemy.active_effects.add(new PinnedEffect(true)); + check.equals(effect.getOnDiffs(enemy, ship).length, 0, "ship cannot be moved"); + enemy.active_effects.remove(pin); + check.equals(effect.getOnDiffs(enemy, ship).length, 1, "ship can be moved again"); + }); + + check.in("sticky hard pin", check => { + let pin = enemy.active_effects.add(new StickyEffect(new PinnedEffect(true))); + check.equals(effect.getOnDiffs(enemy, ship).length, 0, "ship cannot be moved"); + enemy.active_effects.remove(pin); + check.equals(effect.getOnDiffs(enemy, ship).length, 1, "ship can be moved again"); + }); + }); + }); +} diff --git a/src/core/effects/PinnedEffect.ts b/src/core/effects/PinnedEffect.ts new file mode 100644 index 0000000..1be60a8 --- /dev/null +++ b/src/core/effects/PinnedEffect.ts @@ -0,0 +1,22 @@ +/// + +module TK.SpaceTac { + /** + * Pin a ship in space, preventing him from moving using its engine + * + * If hard pinned, the ship also may not be moved by another MoveEffect + */ + export class PinnedEffect extends BaseEffect { + constructor(readonly hard = false) { + super("pinned"); + } + + isBeneficial(): boolean { + return false; + } + + getDescription(): string { + return this.hard ? "anchored" : "pinned"; + } + } +} diff --git a/src/core/effects/RepelEffect.ts b/src/core/effects/RepelEffect.ts index 5beb232..9c92640 100644 --- a/src/core/effects/RepelEffect.ts +++ b/src/core/effects/RepelEffect.ts @@ -14,7 +14,7 @@ module TK.SpaceTac { } getOnDiffs(ship: Ship, source: Ship | Drone): BaseBattleDiff[] { - if (ship != source) { + if (ship != source && !any(ship.getEffects(), effect => effect instanceof PinnedEffect && effect.hard)) { let angle = arenaAngle(source.location, ship.location); let destination = new ArenaLocation(ship.arena_x + Math.cos(angle) * this.value, ship.arena_y + Math.sin(angle) * this.value); let exclusions = ExclusionAreas.fromShip(ship); diff --git a/src/core/models/ModelFlea.ts b/src/core/models/ModelFlea.ts index a46c306..92ea6c2 100644 --- a/src/core/models/ModelFlea.ts +++ b/src/core/models/ModelFlea.ts @@ -12,7 +12,7 @@ module TK.SpaceTac { getLevelUpgrades(level: number): ShipUpgrade[] { if (level == 1) { - let engine = new MoveAction("Engine", { + let engine = new MoveAction("Main Engine", { distance_per_power: 420, }); @@ -23,12 +23,19 @@ module TK.SpaceTac { }, "powerdepleter"); depleter.configureCooldown(1, 1); - let gatling = new TriggerAction("Shield Basher", { + let shield_basher = new TriggerAction("Shield Basher", { effects: [new DamageEffect(2, DamageEffectMode.SHIELD_ONLY, false)], power: 3, range: 300, - }, "submunitionmissile"); - gatling.configureCooldown(2, 1); + }, "shieldbash"); + shield_basher.configureCooldown(2, 1); + + let engine_hijack = new TriggerAction("Engine Hijacking", { + effects: [new StickyEffect(new PinnedEffect(), 2)], + power: 2, + range: 400, + }, "pin"); + engine_hijack.configureCooldown(1, 2); return [ { @@ -42,16 +49,20 @@ module TK.SpaceTac { ] }, { - code: "Main Engine", + code: engine.name, actions: [engine] }, { - code: "Power Depleter", + code: depleter.name, actions: [depleter] }, { - code: "Gatling Gun", - actions: [gatling] + code: shield_basher.name, + actions: [shield_basher] + }, + { + code: engine_hijack.name, + actions: [engine_hijack] }, ]; } else {