1
0
Fork 0

Switched to individual power points display in actionbar

This commit is contained in:
Michaël Lemaire 2017-02-19 22:33:07 +01:00
parent fc483c1967
commit 8b2a4ea026
18 changed files with 222 additions and 89 deletions

2
TODO
View file

@ -2,7 +2,7 @@
* Highlight ships that will be included as target of current action
* Do not focus on ship while targetting for area effects (dissociate hover and target)
* Active effects are not enough visible in ship list (maybe better in arena ?)
* Discrete power display, instead of the continuous power bar
* All things displayed in battle should be updated from BattleLog, not from current state
* Drones: add tooltip
* Drones: add hull points and take area damage
* More sound effects

View file

@ -890,11 +890,11 @@
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="0.79411404"
inkscape:cx="713.78747"
inkscape:cy="706.929"
inkscape:zoom="3.1764562"
inkscape:cx="257.62353"
inkscape:cy="1025.9327"
inkscape:document-units="px"
inkscape:current-layer="layer32"
inkscape:current-layer="layer8"
showgrid="false"
units="px"
showguides="true"
@ -1789,13 +1789,34 @@
style="display:inline">
<path
style="opacity:1;fill:#ffdd4b;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter5649)"
d="M 206.22794,119.86752 H 1891.0512 l 16.8238,-12.12184 H 189.40423 Z"
d="m 197.49714,119.86752 h 51.83694 l 5.86374,-12.12184 h -51.83694 z"
id="rect4437"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/tmp/export.png"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/battle/power-available.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
inkscape:export-ydpi="90"
transform="matrix(0.98149613,0,0,1.5780874,-2.5439867,-65.79016)" />
<path
transform="matrix(0.98149613,0,0,1.5780874,52.760346,-65.79016)"
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/battle/power-using.png"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path4925"
d="m 197.49714,119.86752 h 51.83694 l 5.86374,-12.12184 h -51.83694 z"
style="opacity:1;fill:#f8f0b5;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
transform="matrix(0.98149613,0,0,1.5780874,108.06471,-65.79016)"
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/battle/power-used.png"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path4927"
d="m 197.49714,119.86752 h 51.83694 l 5.86374,-12.12184 h -51.83694 z"
style="opacity:1;fill:#6b6443;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:16;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter5649)" />
</g>
</g>
<g
@ -2124,7 +2145,7 @@
inkscape:label="Action icon active"
style="display:inline">
<ellipse
style="opacity:1;fill:#97d3fb;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10.39999962;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;"
style="opacity:1;fill:#97d3fb;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10.39999962;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path10840"
cx="285.67114"
cy="-51.10569"

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,020 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,009 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

View file

@ -1,4 +1,9 @@
module TS.SpaceTac {
/**
* Function called to inform subscribers of new events.
*/
export type LogSubscriber = (event: BaseLogEvent) => any;
// Log of a battle
// This keeps track of all events in a battle
// It also allows to register a callback to receive these events
@ -7,7 +12,7 @@ module TS.SpaceTac {
events: BaseLogEvent[];
// List of subscribers
private subscribers: Function[];
private subscribers: LogSubscriber[];
// List of event codes to ignore
private filters: string[];
@ -43,7 +48,7 @@ module TS.SpaceTac {
this.events.push(event);
this.subscribers.forEach((subscriber: Function) => {
this.subscribers.forEach(subscriber => {
subscriber(event);
});
}
@ -54,14 +59,14 @@ module TS.SpaceTac {
}
// Subscribe a callback to receive further events
subscribe(callback: (event: BaseLogEvent) => void): Function {
subscribe(callback: LogSubscriber): LogSubscriber {
this.subscribers.push(callback);
return callback;
}
// Unsubscribe a callback
// Pass the value returned by 'subscribe' as argument
unsubscribe(callback: Function): void {
unsubscribe(callback: LogSubscriber): void {
var index = this.subscribers.indexOf(callback);
if (index >= 0) {
this.subscribers.splice(index, 1);

View file

@ -3,8 +3,13 @@
module TS.SpaceTac {
// Battle event, when a ship turn ended, and advanced to a new one
export class ShipChangeEvent extends BaseLogEvent {
// Ship that starts playing
new_ship: Ship;
constructor(ship: Ship, new_ship: Ship) {
super("ship_change", ship, new_ship ? Target.newFromShip(new_ship) : null);
this.new_ship = new_ship;
}
}
}

View file

@ -36,10 +36,9 @@ module TS.SpaceTac.UI {
this.loadImage("battle/action-active.png");
this.loadImage("battle/action-selected.png");
this.loadImage("battle/action-tooltip.png");
this.loadImage("battle/actionpointsnone.png");
this.loadImage("battle/actionpointsempty.png");
this.loadImage("battle/actionpointsfull.png");
this.loadImage("battle/actionpointspart.png");
this.loadImage("battle/power-available.png");
this.loadImage("battle/power-using.png");
this.loadImage("battle/power-used.png");
this.loadImage("battle/ship-tooltip-own.png");
this.loadImage("battle/ship-tooltip-enemy.png");
this.loadImage("battle/ship-tooltip-effect.png");

View file

@ -8,25 +8,25 @@ module TS.SpaceTac.UI.Specs {
// Ship not owned by current battleview player
var ship = new Ship();
bar.setShip(ship);
expect(bar.actions.length).toBe(0);
expect(bar.action_icons.length).toBe(0);
// Ship with no equipment (only endturn action)
battleview.player = ship.getPlayer();
bar.setShip(ship);
expect(bar.actions.length).toBe(1);
expect(bar.actions[0].action.code).toEqual("endturn");
expect(bar.action_icons.length).toBe(1);
expect(bar.action_icons[0].action.code).toEqual("endturn");
// Add an engine, with move action
ship.addSlot(SlotType.Engine).attach((new Equipments.ConventionalEngine()).generate());
bar.setShip(ship);
expect(bar.actions.length).toBe(2);
expect(bar.actions[0].action.code).toEqual("move");
expect(bar.action_icons.length).toBe(2);
expect(bar.action_icons[0].action.code).toEqual("move");
// Add a weapon, with fire action
ship.addSlot(SlotType.Weapon).attach((new Equipments.GatlingGun()).generate());
bar.setShip(ship);
expect(bar.actions.length).toBe(3);
expect(bar.actions[1].action.code).toEqual("fire-gatlinggun");
expect(bar.action_icons.length).toBe(3);
expect(bar.action_icons[1].action.code).toEqual("fire-gatlinggun");
});
inbattleview_it("mark actions that would become unavailable after use", (battleview: BattleView) => {
@ -51,54 +51,95 @@ module TS.SpaceTac.UI.Specs {
ship.setValue("power", 9);
bar.setShip(ship);
expect(bar.actions.length).toBe(4);
expect(bar.action_icons.length).toBe(4);
var checkFading = (fading: number[], available: number[]) => {
fading.forEach((index: number) => {
var icon = bar.actions[index];
var icon = bar.action_icons[index];
expect(icon.fading || !icon.active).toBe(true);
});
available.forEach((index: number) => {
var icon = bar.actions[index];
var icon = bar.action_icons[index];
expect(icon.fading).toBe(false);
});
};
// Weapon 1 leaves all choices open
bar.actions[1].processClick();
bar.action_icons[1].processClick();
checkFading([], [0, 1, 2, 3]);
bar.actionEnded();
// Weapon 2 can't be fired twice
bar.actions[2].processClick();
bar.action_icons[2].processClick();
checkFading([2], [0, 1, 3]);
bar.actionEnded();
// Not enough AP for both weapons
ship.setValue("power", 7);
bar.actions[2].processClick();
bar.action_icons[2].processClick();
checkFading([1, 2], [0, 3]);
bar.actionEnded();
// Not enough AP to move
ship.setValue("power", 3);
bar.actions[1].processClick();
bar.action_icons[1].processClick();
checkFading([0, 1, 2], [3]);
bar.actionEnded();
// Dynamic AP usage for move actions
ship.setValue("power", 6);
bar.actions[0].processClick();
bar.action_icons[0].processClick();
checkFading([], [0, 1, 2, 3]);
bar.actions[0].processHover(Target.newFromLocation(2, 8));
bar.action_icons[0].processHover(Target.newFromLocation(2, 8));
checkFading([2], [0, 1, 3]);
bar.actions[0].processHover(Target.newFromLocation(3, 8));
bar.action_icons[0].processHover(Target.newFromLocation(3, 8));
checkFading([1, 2], [0, 3]);
bar.actions[0].processHover(Target.newFromLocation(4, 8));
bar.action_icons[0].processHover(Target.newFromLocation(4, 8));
checkFading([0, 1, 2], [3]);
bar.actions[0].processHover(Target.newFromLocation(5, 8));
bar.action_icons[0].processHover(Target.newFromLocation(5, 8));
checkFading([0, 1, 2], [3]);
bar.actionEnded();
});
inbattleview_it("updates power points display", battleview => {
let bar = battleview.action_bar;
function check(available = 0, using = 0, used = 0) {
expect(bar.power.children.length).toBe(available + using + used);
bar.power.children.forEach((child, idx) => {
let img = <Phaser.Image>child;
if (idx < available) {
expect(img.name).toEqual("battle-power-available");
} else if (idx < available + using) {
expect(img.name).toEqual("battle-power-using");
} else {
expect(img.name).toEqual("battle-power-used");
}
});
}
// not owned ship
let ship = new Ship();
TestTools.setShipAP(ship, 8);
bar.setShip(ship);
check();
// owned ship
ship.fleet = battleview.player.fleet;
bar.setShip(ship);
check(8);
// used points
ship.setValue("power", 6);
check(6, 0, 2);
// using points
bar.updatePower(5);
check(1, 5, 2);
// decrease
ship.setAttribute("power_capacity", 3);
check(3);
});
});
}

View file

@ -5,12 +5,11 @@ module TS.SpaceTac.UI {
battleview: BattleView;
// List of action icons
group: Phaser.Group;
actions: ActionIcon[];
actions: Phaser.Group;
action_icons: ActionIcon[];
// Progress bar displaying action points
actionpoints: ValueBar;
actionpointstemp: ValueBar;
// Power bar
power: Phaser.Group;
// Tooltip to display hovered action info
tooltip: ActionTooltip;
@ -20,6 +19,8 @@ module TS.SpaceTac.UI {
// Current ship, whose actions are displayed
ship: Ship;
ship_power_capacity: number;
ship_power_value: number;
// Interactivity
interactive = false;
@ -29,7 +30,7 @@ module TS.SpaceTac.UI {
super(battleview.game);
this.battleview = battleview;
this.actions = [];
this.action_icons = [];
this.ship = null;
battleview.ui.add(this);
@ -37,17 +38,13 @@ module TS.SpaceTac.UI {
// Background
this.addChild(new Phaser.Image(this.game, 0, 0, "battle-actionbar", 0));
// Action points progress bar
this.actionpoints = new ValueBar(this.game, 190, 108, "battle-actionpointsempty");
this.actionpoints.setBarImage("battle-actionpointspart");
this.addChild(this.actionpoints);
this.actionpointstemp = new ValueBar(this.game, 190, 108, "battle-actionpointsnone");
this.actionpointstemp.setBarImage("battle-actionpointsfull");
this.addChild(this.actionpointstemp);
// Power bar
this.power = this.game.add.group();
this.addChild(this.power);
// Group for actions
this.group = new Phaser.Group(this.game);
this.addChild(this.group);
this.actions = new Phaser.Group(this.game);
this.addChild(this.actions);
// Waiting icon
this.icon_waiting = new Phaser.Image(this.game, this.width / 2, 50, "battle-waiting", 0);
@ -73,6 +70,23 @@ module TS.SpaceTac.UI {
battleview.inputs.bind(Phaser.Keyboard.EIGHT, "Action 8", () => this.keyActionPressed(7));
battleview.inputs.bind(Phaser.Keyboard.NINE, "Action 9", () => this.keyActionPressed(8));
battleview.inputs.bind(Phaser.Keyboard.ZERO, "Action 10", () => this.keyActionPressed(9));
// Log processing
battleview.log_processor.register(event => {
if (event instanceof ShipChangeEvent) {
this.setShip(event.new_ship);
} else if (event instanceof ValueChangeEvent) {
if (event.ship == this.ship) {
if (event.value.name == SHIP_ATTRIBUTES.power_capacity.name) {
this.ship_power_capacity = event.value.get();
this.updatePower();
} else if (event.value.name == SHIP_VALUES.power.name) {
this.ship_power_value = event.value.get();
this.updatePower();
}
}
}
});
}
/**
@ -90,60 +104,86 @@ module TS.SpaceTac.UI {
keyActionPressed(position: number) {
if (this.interactive) {
if (position < 0) {
this.actions[this.actions.length - 1].processClick();
} else if (position < this.actions.length) {
this.actions[position].processClick();
this.action_icons[this.action_icons.length - 1].processClick();
} else if (position < this.action_icons.length) {
this.action_icons[position].processClick();
}
}
}
// Clear the action icons
clearAll(): void {
this.actions.forEach((action: ActionIcon) => {
this.action_icons.forEach((action: ActionIcon) => {
action.destroy();
});
this.actions = [];
this.action_icons = [];
this.tooltip.setAction(null);
}
// Add an action icon
addAction(ship: Ship, action: BaseAction): ActionIcon {
var icon = new ActionIcon(this, 192 + this.actions.length * 88, 8, ship, action);
this.actions.push(icon);
var icon = new ActionIcon(this, 192 + this.action_icons.length * 88, 8, ship, action);
this.action_icons.push(icon);
this.tooltip.bringToTop();
return icon;
}
// Update the action points indicator
updateActionPoints(): void {
if (this.ship) {
this.actionpoints.setValue(this.ship.values.power.get(), this.ship.attributes.power_capacity.get());
this.actionpointstemp.setValue(this.ship.values.power.get(), this.ship.attributes.power_capacity.get());
this.actionpoints.visible = true;
this.actionpointstemp.visible = true;
} else {
this.actionpoints.visible = false;
this.actionpointstemp.visible = false;
/**
* Update the power indicator
*/
updatePower(selected_action = 0): void {
let current_power = this.power.children.length;
let power_capacity = this.ship_power_capacity;
if (current_power > power_capacity) {
range(current_power - power_capacity).forEach(i => this.power.removeChildAt(current_power - 1 - i));
//this.power.removeChildren(ship_power, current_power); // TODO bugged in phaser 2.6
} else if (power_capacity > current_power) {
range(power_capacity - current_power).forEach(i => this.game.add.image(190 + (current_power + i) * 56, 104, "battle-power-used", 0, this.power));
}
let power_value = this.ship_power_value;
let remaining_power = power_value - selected_action;
this.power.children.forEach((obj, idx) => {
let img = <Phaser.Image>obj;
let key: string;
if (idx < remaining_power) {
key = "battle-power-available";
} else if (idx < power_value) {
key = "battle-power-using";
} else {
key = "battle-power-used"
}
img.name = key;
img.loadTexture(key);
});
}
// Update fading flags
// ap_usage is the consumption of currently selected action
updateFadings(ap_usage: number): void {
var remaining_ap = this.ship.values.power.get() - ap_usage;
/**
* Set current action power usage.
*
* When an action is selected, this will fade the icons not available after the action would be done.
* This will also highlight power usage in the power bar.
*
* *power_usage* is the consumption of currently selected action.
*/
updateSelectedActionPower(power_usage: number): void {
var remaining_ap = this.ship.values.power.get() - power_usage;
if (remaining_ap < 0) {
remaining_ap = 0;
}
this.actions.forEach((icon: ActionIcon) => {
this.action_icons.forEach((icon: ActionIcon) => {
icon.updateFadingStatus(remaining_ap);
});
this.actionpointstemp.setValue(remaining_ap, this.ship.attributes.power_capacity.get());
this.updatePower(power_usage);
}
// Set action icons from selected ship
/**
* Set the bar to display a given ship
*/
setShip(ship: Ship): void {
this.clearAll();
@ -154,13 +194,18 @@ module TS.SpaceTac.UI {
});
this.ship = ship;
this.ship_power_capacity = ship.getAttribute("power_capacity");
this.ship_power_value = ship.getValue("power");
this.game.tweens.create(this).to({ "alpha": 1 }, 400).start();
} else {
this.ship = null;
this.ship_power_capacity = 0;
this.ship_power_value = 0;
this.game.tweens.create(this).to({ "alpha": 0.5 }, 400).start();
}
this.updateActionPoints();
this.updatePower();
this.setInteractive(this.ship != null);
}
// Called by an action icon when the action is selected
@ -169,8 +214,9 @@ module TS.SpaceTac.UI {
// Called by an action icon when the action has been applied
actionEnded(): void {
this.updateActionPoints();
this.actions.forEach((action: ActionIcon) => {
// TODO Lock interactivity until animation is ended
this.updatePower();
this.action_icons.forEach((action: ActionIcon) => {
action.resetState();
});
this.battleview.exitTargettingMode();

View file

@ -43,7 +43,7 @@ module TS.SpaceTac.UI {
this.ship = ship;
this.action = action;
bar.group.addChild(this);
bar.actions.addChild(this);
// Active layer
this.active = false;
@ -104,7 +104,7 @@ module TS.SpaceTac.UI {
this.battleview.arena.range_hint.setPrimary(this.ship, this.action);
// Update fading statuses
this.bar.updateFadings(this.action.getActionPointsUsage(this.battleview.battle, this.ship, null));
this.bar.updateSelectedActionPower(this.action.getActionPointsUsage(this.battleview.battle, this.ship, null));
// Set the selected state
this.setSelected(true);
@ -129,7 +129,7 @@ module TS.SpaceTac.UI {
processHover(target: Target): void {
target = this.action.checkTarget(this.battleview.battle, this.ship, target);
this.targetting.setTarget(target, false, this.action.getBlastRadius(this.ship));
this.bar.updateFadings(this.action.getActionPointsUsage(this.battleview.battle, this.ship, target));
this.bar.updateSelectedActionPower(this.action.getActionPointsUsage(this.battleview.battle, this.ship, target));
}
// Called when a target is selected

View file

@ -59,7 +59,7 @@ module TS.SpaceTac.UI {
this.cost.setText(cost);
this.description.setText(action.action.equipment ? action.action.equipment.getActionDescription() : "");
let position = this.bar.actions.indexOf(action);
let position = this.bar.action_icons.indexOf(action);
if (action.action instanceof EndTurnAction) {
this.shortcut.setText("[ space ]");
} else if (position == 9) {

View file

@ -52,6 +52,8 @@ module TS.SpaceTac.UI {
this.background = null;
this.battle.timer = this.timer;
this.log_processor = new LogProcessor(this);
}
// Create view graphics
@ -79,9 +81,6 @@ module TS.SpaceTac.UI {
this.ship_tooltip = new ShipTooltip(this);
this.add.existing(this.ship_tooltip);
// Start processing the battle log
this.log_processor = new LogProcessor(this);
// "Battle" animation
this.displayFightMessage();
@ -92,6 +91,9 @@ module TS.SpaceTac.UI {
this.inputs.bindCheat(Phaser.Keyboard.W, "Win current battle", () => {
this.battle.endBattle(this.player.fleet);
});
// Inject initial events in battle to populate the LogProcessor
this.battle.injectInitialEvents();
}
// Leaving the view, we unbind the battle
@ -180,7 +182,6 @@ module TS.SpaceTac.UI {
// Disable interaction when it is the AI turn, or when the current ship can't play
setInteractionEnabled(enabled: boolean): void {
this.exitTargettingMode();
this.action_bar.setInteractive(enabled);
this.interacting = enabled;
}

View file

@ -20,13 +20,25 @@ module TS.SpaceTac.UI {
// Processing queue, when delay is active
private queue: BaseLogEvent[] = [];
// Forward events to other subscribers
private forwarding: LogSubscriber[] = [];
constructor(view: BattleView) {
this.view = view;
this.battle = view.battle;
this.log = view.battle.log;
this.subscription = this.log.subscribe(event => this.processBattleEvent(event));
this.battle.injectInitialEvents();
}
/**
* Register a sub-subscriber.
*
* The difference with registering directly to the BattleLog is that events may be delayed
* for animations.
*/
register(callback: LogSubscriber) {
this.forwarding.push(callback);
}
/**
@ -61,6 +73,8 @@ module TS.SpaceTac.UI {
console.log("Battle event", event);
this.forwarding.forEach(subscriber => subscriber(event));
if (event instanceof ShipChangeEvent) {
this.processShipChangeEvent(event);
} else if (event instanceof MoveEvent) {
@ -98,7 +112,6 @@ module TS.SpaceTac.UI {
private processShipChangeEvent(event: ShipChangeEvent): void {
this.view.arena.setShipPlaying(event.target.ship);
this.view.ship_list.setPlaying(event.target.ship);
this.view.action_bar.setShip(event.target.ship);
if (this.battle.canPlay(this.view.player)) {
// Player turn
@ -137,7 +150,9 @@ module TS.SpaceTac.UI {
// Ship value changed
private processValueChangedEvent(event: ValueChangeEvent): void {
var sprite = this.view.arena.findShipSprite(event.ship);
sprite.displayValueChanged(event);
if (sprite) {
sprite.displayValueChanged(event);
}
var item = this.view.ship_list.findItem(event.ship);
if (item) {