1
0
Fork 0

Improved cooldown representation

This commit is contained in:
Michaël Lemaire 2017-05-22 18:29:04 +02:00
parent 03fd66de4c
commit 52d4e1274c
20 changed files with 230 additions and 101 deletions

View file

@ -109,8 +109,8 @@ item is then temporarily disabled (no more effects and cannot be used), until th
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.
overheat. It then needs three "end of turns" to cool down and be available again. 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.

7
TODO
View file

@ -20,11 +20,12 @@
* Arena: display effects description instead of attribute changes
* Arena: add auto-move to attack
* Arena: fix effects originating from real ship location instead of current sprite (when AI fires then moves)
* Actions: separate overheat and cooldown display
* Actions: show power usage/recovery in power bar, on action hover
* Actions: fix targetting not resetting when using keyboard shortcuts
* Suspend AI operation when the game is paused (window not focused)
* Add permanent effects to ship models to ease balancing
* Add actions with cost dependent of distance (like current move actions)
* Keep move actions out of arena borders
* Find incentives to move from starting position
* Outcome: add battle statistics and/or honors
* Outcome: disable the loot button if there is no loot
@ -54,8 +55,10 @@
* Campaign: Missions/quests system
* Campaign: Main story arc
Weapon ideas:
Equipment ideas:
* Shield with toggle effect that absorbs damage in an area
* Shield stealing ability
* Drive with minimal distance
* Cooldown drone or ability
Later, if possible:

View file

@ -1069,6 +1069,84 @@
y="121.98493"
transform="rotate(45)" />
</mask>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Thin glory"
id="filter6668-3">
<feFlood
flood-opacity="0.717647"
flood-color="rgb(255,255,255)"
result="flood"
id="feFlood6658-6" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="out"
result="composite1"
id="feComposite6660-7" />
<feGaussianBlur
in="composite1"
stdDeviation="0.3"
result="blur"
id="feGaussianBlur6662-5" />
<feOffset
dx="0.5"
dy="0.6"
result="offset"
id="feOffset6664-3" />
<feComposite
in="offset"
in2="SourceGraphic"
operator="atop"
result="fbSourceGraphic"
id="feComposite6666-5" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix6670-6" />
<feFlood
id="feFlood6672-2"
flood-opacity="0.717647"
flood-color="rgb(255,255,255)"
result="flood"
in="fbSourceGraphic" />
<feComposite
in2="fbSourceGraphic"
id="feComposite6674-9"
in="flood"
operator="out"
result="composite1" />
<feGaussianBlur
id="feGaussianBlur6676-1"
in="composite1"
stdDeviation="0.3"
result="blur" />
<feOffset
id="feOffset6678-2"
dx="0.5"
dy="0.6"
result="offset" />
<feComposite
in2="fbSourceGraphic"
id="feComposite6680-7"
in="offset"
operator="atop"
result="composite2" />
</filter>
<filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter5347"
x="-0.0288"
width="1.0576"
y="-0.0288"
height="1.0576">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="0.68625"
id="feGaussianBlur5349" />
</filter>
</defs>
<sodipodi:namedview
id="base"
@ -1077,11 +1155,11 @@
borderopacity="1"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="394.77432"
inkscape:cy="563.06617"
inkscape:zoom="8"
inkscape:cx="302.42634"
inkscape:cy="1186.5897"
inkscape:document-units="px"
inkscape:current-layer="g4971-3"
inkscape:current-layer="layer19"
showgrid="false"
units="px"
showguides="false"
@ -2531,35 +2609,37 @@
<g
inkscape:groupmode="layer"
id="layer19"
inkscape:label="Action icon cooldown">
inkscape:label="Action icon cooldown"
style="display:inline">
<g
id="g6298"
transform="translate(1.1292411)">
id="g5353"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/battle/action-cooldown.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<circle
r="7.2920384"
cy="-83.97599"
cx="231.15662"
id="path4989"
style="display:inline;fill:#98301c;fill-opacity:1;fill-rule:evenodd;stroke:#cda9eb;stroke-width:0.9375px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.36595746;filter:url(#filter6668);enable-background:new" />
<use
transform="translate(19.970248)"
height="100%"
width="100%"
id="use4994"
xlink:href="#path4989"
y="0"
x="0"
style="display:inline;enable-background:new" />
<use
transform="translate(19.970236)"
height="100%"
width="100%"
id="use4996"
xlink:href="#use4994"
y="0"
x="0"
style="display:inline;enable-background:new" />
r="28.59375"
cy="-50.982056"
cx="252.2561"
id="path4218"
style="fill:#545252;fill-opacity:0.72765958;fill-rule:evenodd;stroke:none;stroke-width:0.9375px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<circle
style="fill:none;fill-opacity:0.72765958;fill-rule:evenodd;stroke:#686868;stroke-width:0.9375px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter5347)"
id="circle5107"
cx="252.2561"
cy="-50.982056"
r="28.59375" />
</g>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:45px;line-height:23.4375px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#aaaaaa;fill-opacity:1;stroke:none;stroke-width:0.9375px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
x="238.54517"
y="-34.282837"
id="text5105"><tspan
sodipodi:role="line"
id="tspan5103"
x="238.54517"
y="-34.282837"
style="fill:#aaaaaa;stroke-width:0.9375px;fill-opacity:1;">2</tspan></text>
</g>
</g>
<g

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 708 B

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -7,7 +7,7 @@ module TS.SpaceTac.Specs {
cooldown.use();
expect(cooldown.canUse()).toBe(true);
cooldown.configure(2, 2);
cooldown.configure(2, 3);
expect(cooldown.canUse()).toBe(true);
cooldown.use();
@ -25,11 +25,14 @@ module TS.SpaceTac.Specs {
cooldown.cool();
expect(cooldown.canUse()).toBe(true);
/*cooldown.configure(1, 0);
cooldown.configure(1, 0);
expect(cooldown.canUse()).toBe(true);
cooldown.use();
expect(cooldown.canUse()).toBe(false);*/
expect(cooldown.canUse()).toBe(false);
cooldown.cool();
expect(cooldown.canUse()).toBe(true);
});
});
}

View file

@ -12,10 +12,10 @@ module TS.SpaceTac {
// Maximum number of uses allowed per turn before overheating (0 for unlimited)
overheat = 0
// Number of turns needed to cooldown when overheated
cooling = 0
// Number of "end turn" needed to cooldown when overheated
cooling = 1
constructor(overheat = 0, cooling = 0) {
constructor(overheat = 0, cooling = 1) {
this.configure(overheat, cooling);
}
@ -41,7 +41,11 @@ module TS.SpaceTac {
* Check the number of uses before overheating
*/
getRemainingUses(): number {
return (this.heat > 0) ? 0 : (this.overheat - this.uses);
if (this.overheat) {
return (this.heat > 0) ? 0 : (this.overheat - this.uses);
} else {
return Infinity;
}
}
/**
@ -49,7 +53,7 @@ module TS.SpaceTac {
*/
configure(overheat: number, cooling: number) {
this.overheat = overheat;
this.cooling = cooling;
this.cooling = Math.max(1, cooling);
this.reset();
}
@ -60,7 +64,7 @@ module TS.SpaceTac {
if (this.overheat) {
this.uses += 1;
if (this.uses >= this.overheat) {
this.heat = this.cooling + 1;
this.heat = this.cooling;
}
}
}
@ -69,10 +73,13 @@ module TS.SpaceTac {
* Apply one cooling-down step if necessary
*/
cool(): void {
this.uses = 0;
if (this.heat > 0) {
this.heat -= 1;
}
if (this.heat == 0) {
this.uses = 0;
}
}
/**

View file

@ -38,7 +38,7 @@ module TS.SpaceTac.Specs {
it("applies equipment cooldown", function () {
let ship = new Ship();
let equipment = new Equipment(SlotType.Weapon);
equipment.cooldown.configure(1, 1);
equipment.cooldown.configure(1, 2);
ship.addSlot(SlotType.Weapon).attach(equipment);
expect(equipment.cooldown.canUse()).toBe(true, 1);

View file

@ -32,27 +32,38 @@ module TS.SpaceTac {
let ship = new Ship();
expect(action.checkCannotBeApplied(ship)).toBe(null);
expect(action.getUsesBeforeOverheat()).toBe(Infinity);
equipment.cooldown.use();
expect(action.checkCannotBeApplied(ship)).toBe(null);
expect(action.getUsesBeforeOverheat()).toBe(Infinity);
equipment.cooldown.configure(2, 2);
equipment.cooldown.configure(2, 3);
expect(action.checkCannotBeApplied(ship)).toBe(null);
expect(action.getUsesBeforeOverheat()).toBe(2);
equipment.cooldown.use();
expect(action.checkCannotBeApplied(ship)).toBe(null);
expect(action.getUsesBeforeOverheat()).toBe(1);
expect(action.getCooldownDuration()).toBe(0);
equipment.cooldown.use();
expect(action.checkCannotBeApplied(ship)).toBe("overheated");
expect(action.getUsesBeforeOverheat()).toBe(0);
expect(action.getCooldownDuration()).toBe(3);
equipment.cooldown.cool();
expect(action.checkCannotBeApplied(ship)).toBe("overheated");
expect(action.getCooldownDuration()).toBe(2);
equipment.cooldown.cool();
expect(action.checkCannotBeApplied(ship)).toBe("overheated");
expect(action.getCooldownDuration()).toBe(1);
equipment.cooldown.cool();
expect(action.checkCannotBeApplied(ship)).toBe(null);
expect(action.getCooldownDuration()).toBe(0);
expect(action.getCooldownDuration(true)).toBe(3);
});
it("wears down equipment and power generators", function () {

View file

@ -21,6 +21,28 @@ module TS.SpaceTac {
this.equipment = equipment;
}
/**
* Get the number of turns this action is unavailable, because of overheating
*/
getCooldownDuration(estimated = false): number {
if (this.equipment) {
return estimated ? this.equipment.cooldown.cooling : this.equipment.cooldown.heat;
} else {
return 0;
}
}
/**
* Get the number of remaining uses before overheat, infinity if there is no overheat
*/
getUsesBeforeOverheat(): number {
if (this.equipment) {
return this.equipment.cooldown.getRemainingUses();
} else {
return Infinity;
}
}
/**
* Check basic conditions to know if the ship can use this action at all
*

View file

@ -13,5 +13,9 @@ module TS.SpaceTac {
battle.advanceToNextShip();
}
}
getEffectsDescription(): string {
return "End the current ship's turn.\nWill also generate power and cool down equipments.";
}
}
}

View file

@ -205,16 +205,16 @@ module TS.SpaceTac.Specs {
let maneuver = new Maneuver(ship, weapon.action, new Target(0, 0));
expect(TacticalAIHelpers.evaluateOverheat(ship, battle, maneuver)).toEqual(0);
weapon.cooldown.configure(1, 0);
weapon.cooldown.configure(1, 1);
expect(TacticalAIHelpers.evaluateOverheat(ship, battle, maneuver)).toEqual(-0.4);
weapon.cooldown.configure(1, 1);
weapon.cooldown.configure(1, 2);
expect(TacticalAIHelpers.evaluateOverheat(ship, battle, maneuver)).toEqual(-0.8);
weapon.cooldown.configure(1, 2);
weapon.cooldown.configure(1, 3);
expect(TacticalAIHelpers.evaluateOverheat(ship, battle, maneuver)).toEqual(-1);
weapon.cooldown.configure(2, 0);
weapon.cooldown.configure(2, 1);
expect(TacticalAIHelpers.evaluateOverheat(ship, battle, maneuver)).toEqual(0);
});
});

View file

@ -218,7 +218,7 @@ module TS.SpaceTac {
*/
static evaluateOverheat(ship: Ship, battle: Battle, maneuver: Maneuver): number {
if (maneuver.action.equipment && maneuver.action.equipment.cooldown.willOverheat()) {
return -Math.min(1, 0.4 * (maneuver.action.equipment.cooldown.cooling + 1));
return -Math.min(1, 0.4 * maneuver.action.equipment.cooldown.cooling);
} else {
return 0;
}

View file

@ -6,7 +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.setCooldown(irepeat(2), irepeat(2));
this.addFireAction(irepeat(3), irepeat(600), 0, [
new EffectTemplate(new DamageEffect(), { base: istep(30, irepeat(5)), span: istep(20, irepeat(5)) })
]);

View file

@ -6,7 +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.setCooldown(irepeat(2), irepeat(3));
this.addFireAction(irepeat(4), istep(500, irepeat(20)), 0, [
new StickyEffectTemplate(new AttributeLimitEffect("power_capacity"), { "value": irepeat(3) }, irepeat(2))
]);

View file

@ -9,7 +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.setCooldown(irepeat(1), istep(3, 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)) })
]);

View file

@ -6,7 +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.setCooldown(irepeat(2), 0);
this.addAttributeEffect("initiative", 1);
this.addMoveAction(istep(200, irepeat(20)));
}

View file

@ -6,7 +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.setCooldown(irepeat(1), 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)) })
]);

View file

@ -2,40 +2,41 @@ module TS.SpaceTac.UI {
// Icon to activate a ship capability (move, fire...)
export class ActionIcon extends Phaser.Button {
// Link to the parent bar
bar: ActionBar;
bar: ActionBar
// Link to the parent battle view
battleview: BattleView;
battleview: BattleView
// Related ship
ship: Ship;
ship: Ship
// Related game action
action: BaseAction;
action: BaseAction
// True if the action can be used
active: boolean;
active: boolean
// True if the action is selected for use
selected: boolean;
selected: boolean
// True if an action is currently selected, and this one won't be available after its use
fading: boolean;
fading: boolean
// Current targetting
private targetting: Targetting | null;
private targetting: Targetting | null
// Action icon - image representing the action
private layer_icon: Phaser.Image;
private layer_icon: Phaser.Image
// Layer applied when the action is active
private layer_active: Phaser.Image;
private layer_active: Phaser.Image
// Layer applied when the action is selected
private layer_selected: Phaser.Image;
private layer_selected: Phaser.Image
// Cooldown indicators
private layer_cooldown: Phaser.Group
private cooldown: Phaser.Image
private cooldown_count: Phaser.Text
// Create an icon for a single ship action
constructor(bar: ActionBar, x: number, y: number, ship: Ship, action: BaseAction, position: number) {
@ -69,8 +70,12 @@ module TS.SpaceTac.UI {
this.addChild(this.layer_icon);
// Cooldown layer
this.layer_cooldown = new Phaser.Group(this.game);
this.addChild(this.layer_cooldown);
this.cooldown = new Phaser.Image(this.game, this.width / 2, this.height / 2, "battle-action-cooldown");
this.cooldown.anchor.set(0.5, 0.5);
this.cooldown_count = new Phaser.Text(this.game, 0, 0, "", { align: "center", font: "36pt Arial", fill: "#aaaaaa" });
this.cooldown_count.anchor.set(0.5, 0.5);
this.cooldown.addChild(this.cooldown_count);
this.addChild(this.cooldown);
// Events
this.battleview.tooltip.bind(this, filler => {
@ -169,23 +174,26 @@ module TS.SpaceTac.UI {
setSelected(selected: boolean) {
this.selected = selected;
this.battleview.animations.setVisible(this.layer_selected, this.selected, 300);
this.updateCooldownStatus();
}
// 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);
});
}
let remaining = this.action.getUsesBeforeOverheat();
if (this.selected && remaining == 1) {
// will overheat, hint at the cooldown time
let cooldown = this.action.getCooldownDuration(true);
this.cooldown.scale.set(0.7);
this.cooldown_count.text = `${cooldown}`;
this.battleview.animations.setVisible(this.cooldown, true, 300);
} else if (remaining == 0) {
// overheated, show cooldown time
let cooldown = this.action.getCooldownDuration(false);
this.cooldown.scale.set(1);
this.cooldown_count.text = `${cooldown}`;
this.battleview.animations.setVisible(this.cooldown, true, 300);
} else {
this.battleview.animations.setVisible(this.cooldown, false, 300);
}
}

View file

@ -34,7 +34,8 @@ module TS.SpaceTac.UI.Specs {
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 ]");
checkText((<any>tooltip).container.content.children[2], "End the current ship's turn.\nWill also generate power and cool down equipments.");
checkText((<any>tooltip).container.content.children[3], "[ space ]");
});
});
}

View file

@ -34,25 +34,15 @@ module TS.SpaceTac.UI {
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";
if (cooldown.heat > 0) {
filler.addText(150, 90, "Cooling down ...", "#c9604c", 20);
} else if (cooldown.willOverheat() && cost != "Not enough power") {
if (cooldown.cooling > 1) {
let turns = cooldown.cooling;
filler.addText(150, 90, `Unavailable for ${turns} turn${turns > 1 ? "s" : ""} if used`, "#c9604c", 20);
} else {
uses_message += ` Unavailable next ${cooldown.heat - 1} turns`;
filler.addText(150, 90, "Unavailable until next turn if used", "#c9604c", 20);
}
} else {
uses_message = uses == 1 ? "Overheats if used" : `${uses} uses before overheat`;
if (cooldown.cooling) {
uses_message += ` (for ${cooldown.cooling} turn${cooldown.cooling ? "s" : ""})`;
}
}
if (uses_message) {
filler.addText(150, 90, uses_message, "#c9604c", 20);
}
}