Added equipment overheat / cooldown
This commit is contained in:
parent
8823c377dc
commit
a8d0369292
23
README.md
23
README.md
|
@ -101,6 +101,25 @@ If an equipped item has a requirement of "time skill >= 2", that the ship has "t
|
|||
that a temporary effect of "time skill -1" is active, the requirement is no longer fulfilled and the equipped
|
||||
item is then temporarily disabled (no more effects and cannot be used), until the "time skill -1" effect is lifted.
|
||||
|
||||
## Equipments
|
||||
|
||||
### Overheat/Cooldown
|
||||
|
||||
Equipments may overheat, and need to cooldown for some time, during which it cannot be used.
|
||||
|
||||
If an equipment has "overheat 2 / cooldown 3", using it twice in the same turn will cause it to
|
||||
overheat. It then cannot be used for the next three turns. Using this equipment only once per turn
|
||||
is safe, and will never overheat it.
|
||||
|
||||
If an equipment has multiple actions associated, any of these actions will increase the shared heat.
|
||||
|
||||
*Not done yet :* Some equipments may have a "cumulative overheat", meaning that the heat is stored between turns, cooling down 1
|
||||
point at the end of turn.
|
||||
|
||||
*Not done yet :* Some equipments may have a "stacked overheat", which
|
||||
is similar to "cumulative overheat", except it does not cool down at
|
||||
the end of turn (it will only start cooling down after being overheated).
|
||||
|
||||
## Drones
|
||||
|
||||
Drones are static objects, deployed by ships, that apply effects in a circular zone around themselves.
|
||||
|
@ -113,5 +132,7 @@ in the surrounding zone, except if less than a battle cycle passed since last ac
|
|||
|
||||
Drones are fully autonomous, and once deployed, are not controlled by their owner ship.
|
||||
|
||||
They are small and cannot be the direct target of weapons. They are not affected by area effects,
|
||||
They are small and cannot be the direct target of weapons.
|
||||
|
||||
*Not done yet :* They are not affected by area effects,
|
||||
except for area damage and area effects specifically designed for drones.
|
||||
|
|
2
TODO
2
TODO
|
@ -15,6 +15,7 @@
|
|||
* Arena: add sticky effects indicators on ships
|
||||
* Arena: add power indicator in ship hover information
|
||||
* Arena: temporarily show ship information when it changes
|
||||
* Arena: display important changes (damages, effects...) instead of attribute changes
|
||||
* Suspend AI operation when the game is paused (window not focused)
|
||||
* Add permanent effects to ship models to ease balancing
|
||||
* Find incentives to move from starting position
|
||||
|
@ -34,7 +35,6 @@
|
|||
* Prevent arena effects information (eg. "shield -36") to overflow out of the arena
|
||||
* Allow to undo last moves
|
||||
* Add critical hit/miss
|
||||
* Add an overheat/cooling system
|
||||
* Add auto-move to attack
|
||||
* Merge identical sticky effects
|
||||
* Allow to skip animations and AI delays in battle
|
||||
|
|
BIN
out/assets/images/battle/action-cooldown.png
Normal file
BIN
out/assets/images/battle/action-cooldown.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 708 B |
Binary file not shown.
Before Width: | Height: | Size: 1.8 KiB |
|
@ -4,7 +4,10 @@ module TS.SpaceTac.Specs {
|
|||
let cooldown = new Cooldown();
|
||||
expect(cooldown.canUse()).toBe(true);
|
||||
|
||||
cooldown.configure(2, 3);
|
||||
cooldown.use();
|
||||
expect(cooldown.canUse()).toBe(true);
|
||||
|
||||
cooldown.configure(2, 2);
|
||||
expect(cooldown.canUse()).toBe(true);
|
||||
|
||||
cooldown.use();
|
||||
|
@ -21,6 +24,12 @@ module TS.SpaceTac.Specs {
|
|||
|
||||
cooldown.cool();
|
||||
expect(cooldown.canUse()).toBe(true);
|
||||
|
||||
/*cooldown.configure(1, 0);
|
||||
expect(cooldown.canUse()).toBe(true);
|
||||
|
||||
cooldown.use();
|
||||
expect(cooldown.canUse()).toBe(false);*/
|
||||
});
|
||||
});
|
||||
}
|
|
@ -15,6 +15,14 @@ module TS.SpaceTac {
|
|||
// Number of turns needed to cooldown when overheated
|
||||
cooling = 0
|
||||
|
||||
constructor(overheat = 0, cooling = 0) {
|
||||
this.configure(overheat, cooling);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `Overheat ${this.overheat} / Cooldown ${this.cooling}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the equipment can be used in regards to heat
|
||||
*/
|
||||
|
@ -22,21 +30,38 @@ module TS.SpaceTac {
|
|||
return this.heat == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the equipment would overheat if used
|
||||
*/
|
||||
willOverheat(): boolean {
|
||||
return this.overheat > 0 && this.uses + 1 >= this.overheat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the number of uses before overheating
|
||||
*/
|
||||
getRemainingUses(): number {
|
||||
return this.overheat - this.uses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the overheat and cooling
|
||||
*/
|
||||
configure(overheat: number, cooling: number) {
|
||||
this.overheat = overheat;
|
||||
this.cooling = cooling;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the equipment, increasing the heat
|
||||
*/
|
||||
use(): void {
|
||||
this.uses += 1;
|
||||
if (this.uses >= this.overheat) {
|
||||
this.heat = this.cooling;
|
||||
if (this.overheat) {
|
||||
this.uses += 1;
|
||||
if (this.uses >= this.overheat) {
|
||||
this.heat = this.cooling + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,5 +74,13 @@ module TS.SpaceTac {
|
|||
this.heat -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cooldown (typically at the end of turn)
|
||||
*/
|
||||
reset(): void {
|
||||
this.uses = 0;
|
||||
this.heat = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,9 @@ module TS.SpaceTac {
|
|||
// Equipment wear due to usage in battles (will lower the sell price)
|
||||
wear = 0
|
||||
|
||||
// Cooldown needed by the equipment
|
||||
cooldown = new Cooldown()
|
||||
|
||||
// Basic constructor
|
||||
constructor(slot: SlotType | null = null, code = "equipment") {
|
||||
this.slot_type = slot;
|
||||
|
@ -88,6 +91,9 @@ module TS.SpaceTac {
|
|||
if (requirements.length > 0) {
|
||||
description = "Requires:\n" + requirements.join("\n") + "\n\n" + description;
|
||||
}
|
||||
if (this.cooldown.overheat > 0) {
|
||||
description = `${this.cooldown}\n\n${description}`;
|
||||
}
|
||||
if (this.wear > 0) {
|
||||
description = (this.wear >= 100 ? "Worn" : "Second hand") + "\n\n" + description;
|
||||
}
|
||||
|
|
|
@ -54,6 +54,23 @@ module TS.SpaceTac.Specs {
|
|||
});
|
||||
});
|
||||
|
||||
it("applies cooldown", function () {
|
||||
let template = new LootTemplate(SlotType.Weapon, "Weapon");
|
||||
template.setCooldown(istep(1), istep(2));
|
||||
|
||||
let result = template.generate(1);
|
||||
expect(result.cooldown.overheat).toBe(1);
|
||||
expect(result.cooldown.cooling).toBe(2);
|
||||
|
||||
result = template.generate(2);
|
||||
expect(result.cooldown.overheat).toBe(2);
|
||||
expect(result.cooldown.cooling).toBe(3);
|
||||
|
||||
result = template.generate(10);
|
||||
expect(result.cooldown.overheat).toBe(10);
|
||||
expect(result.cooldown.cooling).toBe(11);
|
||||
});
|
||||
|
||||
it("applies attributes permenant effects", function () {
|
||||
let template = new LootTemplate(SlotType.Shield, "Shield");
|
||||
template.addAttributeEffect("shield_capacity", irange(undefined, 50, 10));
|
||||
|
|
|
@ -149,6 +149,11 @@ module TS.SpaceTac {
|
|||
simpleFactor(equipment.action, 'distance_per_power');
|
||||
}
|
||||
|
||||
if (equipment.cooldown.overheat) {
|
||||
simpleFactor(equipment.cooldown, 'overheat', true);
|
||||
simpleFactor(equipment.cooldown, 'cooling', true);
|
||||
}
|
||||
|
||||
// Choose a random one
|
||||
if (modifiers.length > 0) {
|
||||
let chosen = random.choice(modifiers);
|
||||
|
@ -248,6 +253,15 @@ module TS.SpaceTac {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the overheat/cooldown
|
||||
*/
|
||||
setCooldown(overheat: LeveledValue, cooldown: LeveledValue): void {
|
||||
this.base_modifiers.push((equipment, level) => {
|
||||
equipment.cooldown.configure(resolveForLevel(overheat, level), resolveForLevel(cooldown, level));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a permanent attribute effect, when the item is equipped.
|
||||
*/
|
||||
|
|
|
@ -35,6 +35,31 @@ module TS.SpaceTac.Specs {
|
|||
expect(ship.arena_angle).toBeCloseTo(3.14159265, 0.00001);
|
||||
});
|
||||
|
||||
it("applies equipment cooldown", function () {
|
||||
let ship = new Ship();
|
||||
let equipment = new Equipment(SlotType.Weapon);
|
||||
equipment.cooldown.configure(1, 1);
|
||||
ship.addSlot(SlotType.Weapon).attach(equipment);
|
||||
|
||||
expect(equipment.cooldown.canUse()).toBe(true, 1);
|
||||
equipment.cooldown.use();
|
||||
expect(equipment.cooldown.canUse()).toBe(false, 2);
|
||||
|
||||
ship.startBattle();
|
||||
expect(equipment.cooldown.canUse()).toBe(true, 3);
|
||||
|
||||
ship.startTurn();
|
||||
equipment.cooldown.use();
|
||||
expect(equipment.cooldown.canUse()).toBe(false, 4);
|
||||
ship.endTurn();
|
||||
expect(equipment.cooldown.canUse()).toBe(false, 5);
|
||||
|
||||
ship.startTurn();
|
||||
expect(equipment.cooldown.canUse()).toBe(false, 6);
|
||||
ship.endTurn();
|
||||
expect(equipment.cooldown.canUse()).toBe(true, 7);
|
||||
});
|
||||
|
||||
it("lists available actions from attached equipment", function () {
|
||||
var ship = new Ship(null, "Test");
|
||||
var actions: BaseAction[];
|
||||
|
|
|
@ -322,6 +322,7 @@ module TS.SpaceTac {
|
|||
this.updateAttributes();
|
||||
this.restoreHealth();
|
||||
this.initializeActionPoints();
|
||||
this.listEquipment().forEach(equipment => equipment.cooldown.reset());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -371,6 +372,9 @@ module TS.SpaceTac {
|
|||
// Apply sticky effects
|
||||
this.sticky_effects.forEach(effect => effect.endTurn(this));
|
||||
this.cleanStickyEffects();
|
||||
|
||||
// Cool down equipment
|
||||
this.listEquipment().forEach(equipment => equipment.cooldown.cool());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,35 @@ module TS.SpaceTac {
|
|||
expect(action.checkCannotBeApplied(ship)).toBe("not enough power");
|
||||
});
|
||||
|
||||
it("check if equipment can be used with overheat", function () {
|
||||
let equipment = new Equipment();
|
||||
let action = new BaseAction("test", "Test", false, equipment);
|
||||
let ship = new Ship();
|
||||
|
||||
expect(action.checkCannotBeApplied(ship)).toBe(null);
|
||||
|
||||
equipment.cooldown.use();
|
||||
expect(action.checkCannotBeApplied(ship)).toBe(null);
|
||||
|
||||
equipment.cooldown.configure(2, 2);
|
||||
expect(action.checkCannotBeApplied(ship)).toBe(null);
|
||||
|
||||
equipment.cooldown.use();
|
||||
expect(action.checkCannotBeApplied(ship)).toBe(null);
|
||||
|
||||
equipment.cooldown.use();
|
||||
expect(action.checkCannotBeApplied(ship)).toBe("overheated");
|
||||
|
||||
equipment.cooldown.cool();
|
||||
expect(action.checkCannotBeApplied(ship)).toBe("overheated");
|
||||
|
||||
equipment.cooldown.cool();
|
||||
expect(action.checkCannotBeApplied(ship)).toBe("overheated");
|
||||
|
||||
equipment.cooldown.cool();
|
||||
expect(action.checkCannotBeApplied(ship)).toBe(null);
|
||||
});
|
||||
|
||||
it("wears down equipment and power generators", function () {
|
||||
let ship = new Ship();
|
||||
TestTools.setShipAP(ship, 10);
|
||||
|
|
|
@ -2,16 +2,16 @@ module TS.SpaceTac {
|
|||
// Base class for action definitions
|
||||
export class BaseAction {
|
||||
// Identifier code for the type of action
|
||||
code: string;
|
||||
code: string
|
||||
|
||||
// Human-readable name
|
||||
name: string;
|
||||
name: string
|
||||
|
||||
// Boolean at true if the action needs a target
|
||||
needs_target: boolean;
|
||||
needs_target: boolean
|
||||
|
||||
// Equipment that triggers this action
|
||||
equipment: Equipment | null;
|
||||
equipment: Equipment | null
|
||||
|
||||
// Create the action
|
||||
constructor(code: string, name: string, needs_target: boolean, equipment: Equipment | null = null) {
|
||||
|
@ -40,11 +40,16 @@ module TS.SpaceTac {
|
|||
remaining_ap = ship.values.power.get();
|
||||
}
|
||||
var ap_usage = this.getActionPointsUsage(ship, null);
|
||||
if (remaining_ap >= ap_usage) {
|
||||
return null;
|
||||
} else {
|
||||
if (remaining_ap < ap_usage) {
|
||||
return "not enough power";
|
||||
}
|
||||
|
||||
// Check cooldown
|
||||
if (this.equipment && !this.equipment.cooldown.canUse()) {
|
||||
return "overheated";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the number of action points the action applied to a target would use
|
||||
|
@ -109,6 +114,8 @@ module TS.SpaceTac {
|
|||
if (this.equipment) {
|
||||
this.equipment.addWear(1);
|
||||
ship.listEquipment(SlotType.Power).forEach(equipment => equipment.addWear(1));
|
||||
|
||||
this.equipment.cooldown.use();
|
||||
}
|
||||
|
||||
this.customApply(ship, checked_target);
|
||||
|
|
|
@ -8,16 +8,16 @@ module TS.SpaceTac.Equipments {
|
|||
expect(equipment.effects).toEqual([new AttributeEffect("shield_capacity", 100)]);
|
||||
|
||||
equipment = template.generate(2);
|
||||
expect(equipment.requirements).toEqual({ "skill_energy": 2 });
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("shield_capacity", 150)]);
|
||||
expect(equipment.requirements).toEqual({ "skill_energy": 3 });
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("shield_capacity", 140)]);
|
||||
|
||||
equipment = template.generate(3);
|
||||
expect(equipment.requirements).toEqual({ "skill_energy": 3 });
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("shield_capacity", 200)]);
|
||||
expect(equipment.requirements).toEqual({ "skill_energy": 5 });
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("shield_capacity", 180)]);
|
||||
|
||||
equipment = template.generate(10);
|
||||
expect(equipment.requirements).toEqual({ "skill_energy": 10 });
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("shield_capacity", 550)]);
|
||||
expect(equipment.requirements).toEqual({ "skill_energy": 19 });
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("shield_capacity", 460)]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ module TS.SpaceTac.Equipments {
|
|||
constructor() {
|
||||
super(SlotType.Shield, "Force Field", "A basic force field, generated by radiating waves of compressed energy");
|
||||
|
||||
this.setSkillsRequirements({ "skill_energy": 1 });
|
||||
this.addAttributeEffect("shield_capacity", istep(100, irepeat(50)));
|
||||
this.setSkillsRequirements({ "skill_energy": istep(1, irepeat(2)) });
|
||||
this.addAttributeEffect("shield_capacity", istep(100, irepeat(40)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ module TS.SpaceTac.Equipments {
|
|||
super(SlotType.Weapon, "Gatling Gun", "Mechanical weapon using loads of metal bullets propelled by guided explosions");
|
||||
|
||||
this.setSkillsRequirements({ "skill_material": 1 });
|
||||
this.setCooldown(irepeat(2), irepeat(1));
|
||||
this.addFireAction(irepeat(3), irepeat(600), 0, [
|
||||
new EffectTemplate(new DamageEffect(), { base: istep(30, irepeat(5)), span: istep(20, irepeat(5)) })
|
||||
]);
|
||||
|
|
|
@ -9,15 +9,15 @@ module TS.SpaceTac.Equipments {
|
|||
|
||||
equipment = template.generate(2);
|
||||
expect(equipment.requirements).toEqual({ "skill_material": 2 });
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("hull_capacity", 250)]);
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("hull_capacity", 220)]);
|
||||
|
||||
equipment = template.generate(3);
|
||||
expect(equipment.requirements).toEqual({ "skill_material": 3 });
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("hull_capacity", 300)]);
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("hull_capacity", 240)]);
|
||||
|
||||
equipment = template.generate(10);
|
||||
expect(equipment.requirements).toEqual({ "skill_material": 10 });
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("hull_capacity", 650)]);
|
||||
expect(equipment.effects).toEqual([new AttributeEffect("hull_capacity", 380)]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ module TS.SpaceTac.Equipments {
|
|||
super(SlotType.Hull, "Iron Hull", "Protective hull, based on layered iron alloys");
|
||||
|
||||
this.setSkillsRequirements({ "skill_material": 1 });
|
||||
this.addAttributeEffect("hull_capacity", istep(200, irepeat(50)));
|
||||
this.addAttributeEffect("hull_capacity", istep(200, irepeat(20)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ module TS.SpaceTac.Equipments {
|
|||
super(SlotType.Weapon, "Power Depleter", "Direct-hit weapon that creates an energy well near the target, sucking its power surplus");
|
||||
|
||||
this.setSkillsRequirements({ "skill_energy": 1 });
|
||||
this.setCooldown(irepeat(2), irepeat(2));
|
||||
this.addFireAction(irepeat(4), istep(500, irepeat(20)), 0, [
|
||||
new StickyEffectTemplate(new AttributeLimitEffect("power_capacity"), { "value": irepeat(3) }, irepeat(2))
|
||||
]);
|
||||
|
|
|
@ -9,6 +9,7 @@ module TS.SpaceTac.Equipments {
|
|||
super(SlotType.Weapon, "Repair Drone", "Drone able to repair small hull breaches, remotely controlled by human pilots");
|
||||
|
||||
this.setSkillsRequirements({ "skill_human": 1 });
|
||||
this.setCooldown(irepeat(1), istep(2, irepeat(0.2)));
|
||||
this.addDroneAction(irepeat(4), istep(300, irepeat(10)), istep(1, irepeat(0.2)), istep(100, irepeat(10)), [
|
||||
new EffectTemplate(new ValueEffect("hull"), { "value": istep(30, irepeat(3)) })
|
||||
]);
|
||||
|
|
|
@ -6,6 +6,7 @@ module TS.SpaceTac.Equipments {
|
|||
super(SlotType.Engine, "Rocket Engine", "First-era conventional deep-space engine, based on gas exhausts pushed through a nozzle");
|
||||
|
||||
this.setSkillsRequirements({ "skill_energy": 1 });
|
||||
this.setCooldown(irepeat(3), 0);
|
||||
this.addAttributeEffect("initiative", 1);
|
||||
this.addMoveAction(istep(200, irepeat(20)));
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ module TS.SpaceTac.Equipments {
|
|||
expect(battle.log.events[3]).toEqual(new DamageEvent(enemy2, 0, 20));
|
||||
|
||||
battle.log.clear();
|
||||
equipment.cooldown.cool();
|
||||
|
||||
// Fire in space
|
||||
target = Target.newFromLocation(2.4, 0);
|
||||
|
@ -79,6 +80,7 @@ module TS.SpaceTac.Equipments {
|
|||
expect(battle.log.events[2]).toEqual(new DamageEvent(enemy2, 10, 10));
|
||||
|
||||
battle.log.clear();
|
||||
equipment.cooldown.cool();
|
||||
|
||||
// Fire far away
|
||||
target = Target.newFromLocation(5, 0);
|
||||
|
|
|
@ -6,6 +6,7 @@ module TS.SpaceTac.Equipments {
|
|||
super(SlotType.Weapon, "SubMunition Missile", "Explosive missile releasing small shelled payloads, that will in turn explode on impact");
|
||||
|
||||
this.setSkillsRequirements({ "skill_material": 1 });
|
||||
this.setCooldown(irepeat(1), irepeat(0));
|
||||
this.addFireAction(irepeat(4), istep(500, irepeat(20)), istep(150, irepeat(5)), [
|
||||
new EffectTemplate(new DamageEffect(), { base: istep(30, irepeat(2)), span: istep(2, irepeat(1)) })
|
||||
]);
|
||||
|
|
|
@ -33,7 +33,7 @@ module TS.SpaceTac.UI {
|
|||
this.loadImage("battle/action-inactive.png");
|
||||
this.loadImage("battle/action-active.png");
|
||||
this.loadImage("battle/action-selected.png");
|
||||
this.loadImage("battle/action-tooltip.png");
|
||||
this.loadImage("battle/action-cooldown.png");
|
||||
this.loadImage("battle/power-available.png");
|
||||
this.loadImage("battle/power-using.png");
|
||||
this.loadImage("battle/power-used.png");
|
||||
|
|
|
@ -104,4 +104,14 @@ module TS.SpaceTac.UI.Specs {
|
|||
return [testgame.mapview, [session.universe, session.player]];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a given text node
|
||||
*/
|
||||
export function checkText(node: any, content: string): void {
|
||||
expect(node instanceof Phaser.Text).toBe(true);
|
||||
|
||||
let tnode = <Phaser.Text>node;
|
||||
expect(tnode.text).toEqual(content);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,6 @@ module TS.SpaceTac.UI {
|
|||
// Power bar
|
||||
power: Phaser.Group;
|
||||
|
||||
// Tooltip to display hovered action info
|
||||
tooltip: ActionTooltip;
|
||||
|
||||
// Indicator of interaction disabled
|
||||
icon_waiting: Phaser.Image;
|
||||
|
||||
|
@ -52,10 +49,6 @@ module TS.SpaceTac.UI {
|
|||
this.icon_waiting.scale.set(0.5, 0.5);
|
||||
this.addChild(this.icon_waiting);
|
||||
|
||||
// Tooltip
|
||||
this.tooltip = new ActionTooltip(this);
|
||||
this.addChild(this.tooltip);
|
||||
|
||||
// Key bindings
|
||||
battleview.inputs.bind("Escape", "Cancel action", () => this.actionEnded());
|
||||
battleview.inputs.bind(" ", "End turn", () => this.keyActionPressed(-1));
|
||||
|
@ -128,16 +121,13 @@ module TS.SpaceTac.UI {
|
|||
action.destroy();
|
||||
});
|
||||
this.action_icons = [];
|
||||
this.tooltip.setAction(null);
|
||||
}
|
||||
|
||||
// Add an action icon
|
||||
addAction(ship: Ship, action: BaseAction): ActionIcon {
|
||||
var icon = new ActionIcon(this, 192 + this.action_icons.length * 88, 8, ship, action);
|
||||
var icon = new ActionIcon(this, 192 + this.action_icons.length * 88, 8, ship, action, this.action_icons.length);
|
||||
this.action_icons.push(icon);
|
||||
|
||||
this.tooltip.bringToTop();
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
|
@ -187,7 +177,7 @@ module TS.SpaceTac.UI {
|
|||
}
|
||||
|
||||
this.action_icons.forEach((icon: ActionIcon) => {
|
||||
icon.updateFadingStatus(remaining_ap);
|
||||
icon.updateFadingStatus(remaining_ap, true);
|
||||
});
|
||||
this.updatePower(power_usage);
|
||||
}
|
||||
|
|
|
@ -34,8 +34,11 @@ module TS.SpaceTac.UI {
|
|||
// Layer applied when the action is selected
|
||||
private layer_selected: Phaser.Image;
|
||||
|
||||
// Cooldown indicators
|
||||
private layer_cooldown: Phaser.Group
|
||||
|
||||
// Create an icon for a single ship action
|
||||
constructor(bar: ActionBar, x: number, y: number, ship: Ship, action: BaseAction) {
|
||||
constructor(bar: ActionBar, x: number, y: number, ship: Ship, action: BaseAction, position: number) {
|
||||
super(bar.game, x, y, "battle-action-inactive");
|
||||
|
||||
this.bar = bar;
|
||||
|
@ -65,22 +68,24 @@ module TS.SpaceTac.UI {
|
|||
this.layer_icon.scale.set(0.25, 0.25);
|
||||
this.addChild(this.layer_icon);
|
||||
|
||||
let show_info = () => {
|
||||
if (this.bar.ship) {
|
||||
this.bar.tooltip.setAction(this);
|
||||
this.battleview.arena.range_hint.setSecondary(this.ship, this.action);
|
||||
}
|
||||
};
|
||||
let hide_info = () => {
|
||||
this.bar.tooltip.setAction(null);
|
||||
this.battleview.arena.range_hint.clearSecondary();
|
||||
};
|
||||
// Cooldown layer
|
||||
this.layer_cooldown = new Phaser.Group(this.game);
|
||||
this.addChild(this.layer_cooldown);
|
||||
|
||||
// Events
|
||||
UITools.setHoverClick(this, show_info, hide_info, () => this.processClick());
|
||||
this.battleview.tooltip.bind(this, filler => {
|
||||
ActionTooltip.fill(filler, this.ship, this.action, position);
|
||||
return true;
|
||||
});
|
||||
UITools.setHoverClick(this,
|
||||
() => this.battleview.arena.range_hint.setSecondary(this.ship, this.action),
|
||||
() => this.battleview.arena.range_hint.clearSecondary(),
|
||||
() => this.processClick()
|
||||
);
|
||||
|
||||
// Initialize
|
||||
this.updateActiveStatus(true);
|
||||
this.updateCooldownStatus();
|
||||
}
|
||||
|
||||
// Process a click event on the action icon
|
||||
|
@ -154,6 +159,7 @@ module TS.SpaceTac.UI {
|
|||
this.targetting = null;
|
||||
}
|
||||
this.setSelected(false);
|
||||
this.updateCooldownStatus();
|
||||
this.updateActiveStatus();
|
||||
this.updateFadingStatus(this.ship.values.power.get());
|
||||
this.battleview.arena.range_hint.clearPrimary();
|
||||
|
@ -165,6 +171,24 @@ module TS.SpaceTac.UI {
|
|||
this.battleview.animations.setVisible(this.layer_selected, this.selected, 300);
|
||||
}
|
||||
|
||||
// Update the cooldown status
|
||||
updateCooldownStatus(): void {
|
||||
this.layer_cooldown.removeAll();
|
||||
if (this.action.equipment) {
|
||||
let cooldown = this.action.equipment.cooldown;
|
||||
let count = cooldown.heat ? cooldown.heat : (cooldown.willOverheat() ? cooldown.cooling + 1 : 0);
|
||||
if (count) {
|
||||
let positions = UITools.evenlySpace(68, 18, count);
|
||||
range(count).forEach(i => {
|
||||
let dot = new Phaser.Image(this.game, 10 + positions[i], 10, "battle-action-cooldown");
|
||||
dot.anchor.set(0.5, 0.5);
|
||||
dot.alpha = cooldown.heat ? 1 : 0.5;
|
||||
this.layer_cooldown.add(dot);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the active status, from the action canBeUsed result
|
||||
updateActiveStatus(force = false): void {
|
||||
var old_active = this.active;
|
||||
|
@ -177,9 +201,10 @@ module TS.SpaceTac.UI {
|
|||
}
|
||||
|
||||
// Update the fading status, given an hypothetical remaining AP
|
||||
updateFadingStatus(remaining_ap: number): void {
|
||||
var old_fading = this.fading;
|
||||
this.fading = this.active && (this.action.checkCannotBeApplied(this.ship, remaining_ap) != null);
|
||||
updateFadingStatus(remaining_ap: number, action = false): void {
|
||||
let old_fading = this.fading;
|
||||
let overheat = action && (this.action.equipment !== null && this.action.equipment.cooldown.willOverheat());
|
||||
this.fading = this.active && (this.action.checkCannotBeApplied(this.ship, remaining_ap) != null || overheat);
|
||||
if (this.fading != old_fading) {
|
||||
this.battleview.animations.setVisible(this.layer_active, this.active && !this.fading, 500);
|
||||
}
|
||||
|
|
|
@ -2,41 +2,39 @@
|
|||
|
||||
module TS.SpaceTac.UI.Specs {
|
||||
describe("ActionTooltip", function () {
|
||||
let testgame = setupBattleview();
|
||||
let testgame = setupEmptyView();
|
||||
|
||||
it("displays action information", () => {
|
||||
let battleview = testgame.battleview;
|
||||
let bar = battleview.action_bar;
|
||||
let tooltip = bar.tooltip;
|
||||
let tooltip = new Tooltip(testgame.baseview);
|
||||
let ship = new Ship();
|
||||
TestTools.setShipAP(ship, 10);
|
||||
|
||||
bar.clearAll();
|
||||
let ship = nn(battleview.battle.playing_ship);
|
||||
let a1 = bar.addAction(ship, new MoveAction(new Equipment()));
|
||||
nn(a1.action.equipment).name = "Engine";
|
||||
a1.action.name = "Move";
|
||||
let a2 = bar.addAction(ship, new FireWeaponAction(new Equipment(), 2, 50, 0, [new DamageEffect(12)]));
|
||||
nn(a2.action.equipment).name = "Weapon";
|
||||
a2.action.name = "Fire";
|
||||
let a3 = bar.addAction(ship, new EndTurnAction());
|
||||
a3.action.name = "End turn";
|
||||
let action1 = new MoveAction(new Equipment());
|
||||
nn(action1.equipment).name = "Engine";
|
||||
action1.name = "Move";
|
||||
let action2 = new FireWeaponAction(new Equipment(), 2, 50, 0, [new DamageEffect(12)]);
|
||||
nn(action2.equipment).name = "Weapon";
|
||||
action2.name = "Fire";
|
||||
let action3 = new EndTurnAction();
|
||||
action3.name = "End turn";
|
||||
|
||||
tooltip.setAction(a1);
|
||||
expect(tooltip.main_title.text).toEqual("Engine");
|
||||
expect(tooltip.sub_title.text).toEqual("Move");
|
||||
expect(tooltip.shortcut.text).toEqual("[ 1 ]");
|
||||
expect(tooltip.description.text).toEqual("Move: 0km per power point");
|
||||
ActionTooltip.fill(tooltip.getFiller(), ship, action1, 0);
|
||||
checkText((<any>tooltip).container.content.children[1], "Engine");
|
||||
checkText((<any>tooltip).container.content.children[2], "Cost: 1 power per 0km");
|
||||
checkText((<any>tooltip).container.content.children[3], "Move: 0km per power point");
|
||||
checkText((<any>tooltip).container.content.children[4], "[ 1 ]");
|
||||
|
||||
tooltip.setAction(a2);
|
||||
expect(tooltip.main_title.text).toEqual("Weapon");
|
||||
expect(tooltip.sub_title.text).toEqual("Fire");
|
||||
expect(tooltip.shortcut.text).toEqual("[ 2 ]");
|
||||
expect(tooltip.description.text).toEqual("Fire (power usage 2, max range 50km):\n• do 12 damage on target");
|
||||
tooltip.hide();
|
||||
ActionTooltip.fill(tooltip.getFiller(), ship, action2, 1);
|
||||
checkText((<any>tooltip).container.content.children[1], "Weapon");
|
||||
checkText((<any>tooltip).container.content.children[2], "Cost: 2 power");
|
||||
checkText((<any>tooltip).container.content.children[3], "Fire (power usage 2, max range 50km):\n• do 12 damage on target");
|
||||
checkText((<any>tooltip).container.content.children[4], "[ 2 ]");
|
||||
|
||||
tooltip.setAction(a3);
|
||||
expect(tooltip.main_title.text).toEqual("End turn");
|
||||
expect(tooltip.sub_title.text).toEqual("");
|
||||
expect(tooltip.shortcut.text).toEqual("[ space ]");
|
||||
expect(tooltip.description.text).toEqual("");
|
||||
tooltip.hide();
|
||||
ActionTooltip.fill(tooltip.getFiller(), ship, action3, 2);
|
||||
checkText((<any>tooltip).container.content.children[1], "End turn");
|
||||
checkText((<any>tooltip).container.content.children[2], "[ space ]");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,79 +1,74 @@
|
|||
module TS.SpaceTac.UI {
|
||||
// Tooltip to display action information
|
||||
export class ActionTooltip extends Phaser.Sprite {
|
||||
bar: ActionBar;
|
||||
icon: Phaser.Image | null;
|
||||
main_title: Phaser.Text;
|
||||
sub_title: Phaser.Text;
|
||||
cost: Phaser.Text;
|
||||
description: Phaser.Text;
|
||||
shortcut: Phaser.Text;
|
||||
/**
|
||||
* Tooltip displaying action information
|
||||
*/
|
||||
export class ActionTooltip {
|
||||
/**
|
||||
* Fill the tooltip
|
||||
*/
|
||||
static fill(filler: TooltipFiller, ship: Ship, action: BaseAction, position: number) {
|
||||
filler.addImage(0, 0, "battle-actions-" + action.code, 0.5);
|
||||
|
||||
constructor(parent: ActionBar) {
|
||||
super(parent.game, 0, 0, "battle-action-tooltip");
|
||||
this.bar = parent;
|
||||
this.visible = false;
|
||||
filler.addText(150, 0, action.equipment ? action.equipment.name : action.name, "#ffffff", 24);
|
||||
|
||||
this.icon = null;
|
||||
|
||||
this.main_title = new Phaser.Text(this.game, 325, 20, "", { font: "24pt Arial", fill: "#ffffff" });
|
||||
this.main_title.anchor.set(0.5, 0);
|
||||
this.addChild(this.main_title);
|
||||
|
||||
this.sub_title = new Phaser.Text(this.game, 325, 60, "", { font: "22pt Arial", fill: "#ffffff" });
|
||||
this.sub_title.anchor.set(0.5, 0);
|
||||
this.addChild(this.sub_title);
|
||||
|
||||
this.cost = new Phaser.Text(this.game, 325, 100, "", { font: "20pt Arial", fill: "#ffff00" });
|
||||
this.cost.anchor.set(0.5, 0);
|
||||
this.addChild(this.cost);
|
||||
|
||||
this.description = new Phaser.Text(this.game, 21, 144, "", { font: "14pt Arial", fill: "#ffffff" });
|
||||
this.description.wordWrap = true;
|
||||
this.description.wordWrapWidth = 476;
|
||||
this.addChild(this.description);
|
||||
|
||||
this.shortcut = new Phaser.Text(this.game, this.width - 5, this.height - 5, "", { font: "12pt Arial", fill: "#aaaaaa" });
|
||||
this.shortcut.anchor.set(1, 1);
|
||||
this.addChild(this.shortcut);
|
||||
}
|
||||
|
||||
// Set current action to display, null to hide
|
||||
setAction(action: ActionIcon | null): void {
|
||||
if (action) {
|
||||
if (this.icon) {
|
||||
this.icon.destroy(true);
|
||||
}
|
||||
this.icon = new Phaser.Image(this.game, 76, 72, "battle-actions-" + action.action.code);
|
||||
this.icon.anchor.set(0.5, 0.5);
|
||||
this.icon.scale.set(0.44, 0.44);
|
||||
this.addChild(this.icon);
|
||||
|
||||
this.position.set(action.x, action.y + action.height + 44);
|
||||
this.main_title.setText(action.action.equipment ? action.action.equipment.name : action.action.name);
|
||||
this.sub_title.setText(action.action.equipment ? action.action.name : "");
|
||||
if (action.action instanceof MoveAction) {
|
||||
this.cost.setText(`Cost: 1 power per ${action.action.distance_per_power}km`);
|
||||
let cost = "";
|
||||
if (action instanceof MoveAction) {
|
||||
if (ship.getValue("power") == 0) {
|
||||
cost = "Not enough power";
|
||||
} else {
|
||||
let cost = action.action.getActionPointsUsage(action.ship, null);
|
||||
this.cost.setText(cost == 0 ? "" : `Cost: ${cost} power`);
|
||||
cost = `Cost: 1 power per ${action.distance_per_power}km`;
|
||||
}
|
||||
this.description.setText(action.action.getEffectsDescription());
|
||||
} else if (action.equipment) {
|
||||
let power_usage = action.getActionPointsUsage(ship, null);
|
||||
if (power_usage) {
|
||||
if (ship.getValue("power") < power_usage) {
|
||||
cost = "Not enough power";
|
||||
} else {
|
||||
cost = `Cost: ${power_usage} power`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cost) {
|
||||
filler.addText(150, 50, cost, "#ffdd4b", 20);
|
||||
}
|
||||
|
||||
let position = this.bar.action_icons.indexOf(action);
|
||||
if (action.action instanceof EndTurnAction) {
|
||||
this.shortcut.setText("[ space ]");
|
||||
} else if (position == 9) {
|
||||
this.shortcut.setText("[ 0 ]");
|
||||
} else if (position >= 0 && position < 9) {
|
||||
this.shortcut.setText(`[ ${position + 1} ]`);
|
||||
if (action.equipment && action.equipment.cooldown.overheat) {
|
||||
let cooldown = action.equipment.cooldown;
|
||||
let uses = cooldown.getRemainingUses();
|
||||
let uses_message = "";
|
||||
if (uses == 0) {
|
||||
uses_message = "Overheated !";
|
||||
if (cooldown.heat == 1) {
|
||||
uses_message += " Available next turn";
|
||||
} else if (cooldown.heat == 2) {
|
||||
uses_message += " Unavailable next turn";
|
||||
} else {
|
||||
uses_message += ` Unavailable next ${cooldown.heat - 1} turns`;
|
||||
}
|
||||
} else {
|
||||
this.shortcut.setText("");
|
||||
uses_message = uses == 1 ? "Overheats if used" : `${uses} uses before overheat`;
|
||||
if (cooldown.cooling) {
|
||||
uses_message += ` (for ${cooldown.cooling} turn${cooldown.cooling ? "s" : ""})`;
|
||||
}
|
||||
}
|
||||
filler.addText(150, 90, uses_message, "#c9604c", 20);
|
||||
}
|
||||
|
||||
this.bar.battleview.animations.show(this, 200, 0.9);
|
||||
} else {
|
||||
this.bar.battleview.animations.hide(this, 200);
|
||||
let description = action.getEffectsDescription();
|
||||
if (description) {
|
||||
filler.addText(0, 150, description, "#ffffff", 14);
|
||||
}
|
||||
|
||||
let shortcut = "";
|
||||
if (action instanceof EndTurnAction) {
|
||||
shortcut = "[ space ]";
|
||||
} else if (position == 9) {
|
||||
shortcut = "[ 0 ]";
|
||||
} else if (position >= 0 && position < 9) {
|
||||
shortcut = `[ ${position + 1} ]`;
|
||||
}
|
||||
if (shortcut) {
|
||||
filler.addText(0, 0, shortcut, "#aaaaaa", 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -219,5 +219,12 @@ module TS.SpaceTac.UI {
|
|||
this.battleview.animations.setVisible(this.battleview.background, !active, 200);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the boundaries of the arena on display
|
||||
*/
|
||||
getBoundaries(): IBounded {
|
||||
return { x: 130, y: 140, width: 1920 - 138, height: 1080 - 148 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ module TS.SpaceTac.UI {
|
|||
|
||||
let filler = this.getFiller();
|
||||
|
||||
filler.configure(10, 6, { x: 130, y: 140, width: 1920 - 138, height: 1080 - 148 });
|
||||
filler.configure(10, 6, this.battleview.arena.getBoundaries());
|
||||
|
||||
filler.addImage(0, 0, `ship-${ship.model.code}-portrait`, 0.5);
|
||||
|
||||
|
|
|
@ -1,17 +1,68 @@
|
|||
module TS.SpaceTac.UI.Specs {
|
||||
describe("UITools", function () {
|
||||
let testgame = setupEmptyView();
|
||||
describe("in UI", function () {
|
||||
let testgame = setupEmptyView();
|
||||
|
||||
it("keeps objects inside bounds", function () {
|
||||
let image = testgame.baseview.add.graphics(150, 100);
|
||||
image.beginFill(0xff0000);
|
||||
image.drawEllipse(50, 25, 50, 25);
|
||||
image.endFill();
|
||||
it("keeps objects inside bounds", function () {
|
||||
let image = testgame.baseview.add.graphics(150, 100);
|
||||
image.beginFill(0xff0000);
|
||||
image.drawEllipse(50, 25, 50, 25);
|
||||
image.endFill();
|
||||
|
||||
UITools.keepInside(image, { x: 0, y: 0, width: 200, height: 200 });
|
||||
UITools.keepInside(image, { x: 0, y: 0, width: 200, height: 200 });
|
||||
|
||||
expect(image.x).toBe(100);
|
||||
expect(image.y).toBe(100);
|
||||
expect(image.x).toBe(100);
|
||||
expect(image.y).toBe(100);
|
||||
});
|
||||
|
||||
it("handles hover and click on desktops and mobile targets", function (done) {
|
||||
jasmine.clock().uninstall();
|
||||
|
||||
let newButton: () => [Phaser.Button, any] = () => {
|
||||
var button = new Phaser.Button(testgame.ui);
|
||||
var funcs = {
|
||||
enter: () => null,
|
||||
leave: () => null,
|
||||
click: () => null,
|
||||
};
|
||||
spyOn(funcs, "enter");
|
||||
spyOn(funcs, "leave");
|
||||
spyOn(funcs, "click");
|
||||
UITools.setHoverClick(button, funcs.enter, funcs.leave, funcs.click, 50, 100);
|
||||
return [button, funcs];
|
||||
}
|
||||
|
||||
// Simple click on desktop
|
||||
let [button, funcs] = newButton();
|
||||
button.onInputOver.dispatch();
|
||||
button.onInputDown.dispatch();
|
||||
button.onInputUp.dispatch();
|
||||
expect(funcs.enter).toHaveBeenCalledTimes(0);
|
||||
expect(funcs.leave).toHaveBeenCalledTimes(0);
|
||||
expect(funcs.click).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Simple click on mobile
|
||||
[button, funcs] = newButton();
|
||||
button.onInputDown.dispatch();
|
||||
button.onInputUp.dispatch();
|
||||
expect(funcs.enter).toHaveBeenCalledTimes(1);
|
||||
expect(funcs.leave).toHaveBeenCalledTimes(1);
|
||||
expect(funcs.click).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Hold to hover on mobile
|
||||
[button, funcs] = newButton();
|
||||
button.onInputDown.dispatch();
|
||||
Timer.global.schedule(150, () => {
|
||||
expect(funcs.enter).toHaveBeenCalledTimes(1);
|
||||
expect(funcs.leave).toHaveBeenCalledTimes(0);
|
||||
expect(funcs.click).toHaveBeenCalledTimes(0);
|
||||
button.onInputUp.dispatch();
|
||||
expect(funcs.enter).toHaveBeenCalledTimes(1);
|
||||
expect(funcs.leave).toHaveBeenCalledTimes(1);
|
||||
expect(funcs.click).toHaveBeenCalledTimes(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes angles", function () {
|
||||
|
@ -23,53 +74,12 @@ module TS.SpaceTac.UI.Specs {
|
|||
expect(UITools.normalizeAngle(-Math.PI - 0.5)).toBeCloseTo(Math.PI - 0.5, 0.000001);
|
||||
});
|
||||
|
||||
it("handles hover and click on desktops and mobile targets", function (done) {
|
||||
jasmine.clock().uninstall();
|
||||
|
||||
let newButton: () => [Phaser.Button, any] = () => {
|
||||
var button = new Phaser.Button(testgame.ui);
|
||||
var funcs = {
|
||||
enter: () => null,
|
||||
leave: () => null,
|
||||
click: () => null,
|
||||
};
|
||||
spyOn(funcs, "enter");
|
||||
spyOn(funcs, "leave");
|
||||
spyOn(funcs, "click");
|
||||
UITools.setHoverClick(button, funcs.enter, funcs.leave, funcs.click, 50, 100);
|
||||
return [button, funcs];
|
||||
}
|
||||
|
||||
// Simple click on desktop
|
||||
let [button, funcs] = newButton();
|
||||
button.onInputOver.dispatch();
|
||||
button.onInputDown.dispatch();
|
||||
button.onInputUp.dispatch();
|
||||
expect(funcs.enter).toHaveBeenCalledTimes(0);
|
||||
expect(funcs.leave).toHaveBeenCalledTimes(0);
|
||||
expect(funcs.click).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Simple click on mobile
|
||||
[button, funcs] = newButton();
|
||||
button.onInputDown.dispatch();
|
||||
button.onInputUp.dispatch();
|
||||
expect(funcs.enter).toHaveBeenCalledTimes(1);
|
||||
expect(funcs.leave).toHaveBeenCalledTimes(1);
|
||||
expect(funcs.click).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Hold to hover on mobile
|
||||
[button, funcs] = newButton();
|
||||
button.onInputDown.dispatch();
|
||||
Timer.global.schedule(150, () => {
|
||||
expect(funcs.enter).toHaveBeenCalledTimes(1);
|
||||
expect(funcs.leave).toHaveBeenCalledTimes(0);
|
||||
expect(funcs.click).toHaveBeenCalledTimes(0);
|
||||
button.onInputUp.dispatch();
|
||||
expect(funcs.enter).toHaveBeenCalledTimes(1);
|
||||
expect(funcs.leave).toHaveBeenCalledTimes(1);
|
||||
expect(funcs.click).toHaveBeenCalledTimes(0);
|
||||
done();
|
||||
});
|
||||
it("spaces items evenly", function () {
|
||||
expect(UITools.evenlySpace(100, 20, 0)).toEqual([]);
|
||||
expect(UITools.evenlySpace(100, 20, 1)).toEqual([50]);
|
||||
expect(UITools.evenlySpace(100, 20, 2)).toEqual([25, 75]);
|
||||
expect(UITools.evenlySpace(100, 20, 5)).toEqual([10, 30, 50, 70, 90]);
|
||||
expect(UITools.evenlySpace(100, 20, 6)).toEqual([10, 26, 42, 58, 74, 90]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -131,5 +131,21 @@ module TS.SpaceTac.UI {
|
|||
return angle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evenly space identical items in a parent
|
||||
*
|
||||
* Returns the relative position of item's center inside parent_width
|
||||
*/
|
||||
static evenlySpace(parent_width: number, item_width: number, item_count: number): number[] {
|
||||
if (item_width * item_count <= parent_width) {
|
||||
let spacing = parent_width / item_count;
|
||||
return range(item_count).map(i => (i + 0.5) * spacing);
|
||||
} else {
|
||||
let breadth = parent_width - item_width;
|
||||
let spacing = breadth / (item_count - 1);
|
||||
return range(item_count).map(i => item_width / 2 + i * spacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue