Start of character sheet
10
TODO
|
@ -1,6 +1,7 @@
|
|||
* Ensure that tweens and particle emitters get destroyed once animation is done (or view changes)
|
||||
* 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)
|
||||
* Controls: Do not focus on ship while targetting for area effects (dissociate hover and target)
|
||||
* Controls: fix hover not working when mouse goes rapidly over and out of a ship
|
||||
* Active effects are not enough visible in ship list (maybe better in arena ?)
|
||||
* All things displayed in battle should be updated from LogProcess forwarding, not from current game state
|
||||
* Drones: add tooltip
|
||||
|
@ -21,8 +22,12 @@
|
|||
* Mobile: display tooltips larger and on the side of screen where the finger is not
|
||||
* Mobile: targetting in two times, using a draggable target indicator
|
||||
* AI: apply safety distances to move actions
|
||||
* AI: bully AI crashes when winning a battle (trying to move toward null ship!)
|
||||
* AI: use support equipments (repair drones...)
|
||||
* AI: fix not being able to apply simulated maneuver
|
||||
* TacticalAI: allow to play several moves in the same turn
|
||||
* TacticalAI: add pauses to not play too quickly
|
||||
* TacticalAI: replace BullyAI
|
||||
* AIDuel: fix first AI always winning when two identical AIs are selected
|
||||
* Add a defeat screen (game over for now)
|
||||
* Add a victory screen, with loot display
|
||||
* Add retreat from battle
|
||||
|
@ -39,6 +44,7 @@
|
|||
* Main story arc
|
||||
|
||||
Later, if possible:
|
||||
* Replays
|
||||
* Multiplayer
|
||||
* Saving to external file
|
||||
* Saving to cloud
|
||||
|
|
|
@ -16,9 +16,12 @@
|
|||
viewBox="0 0 507.99999 285.75001"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.1 unknown"
|
||||
inkscape:version="0.92.0 r15299"
|
||||
sodipodi:docname="character.svg"
|
||||
enable-background="new">
|
||||
enable-background="new"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/character/close.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2">
|
||||
<inkscape:tag
|
||||
|
@ -43,6 +46,9 @@
|
|||
<inkscape:tagref
|
||||
xlink:href="#g6665"
|
||||
id="tagref15396" />
|
||||
<inkscape:tagref
|
||||
xlink:href="#text6771"
|
||||
id="tagref5459" />
|
||||
</inkscape:tag>
|
||||
<linearGradient
|
||||
id="linearGradient9404"
|
||||
|
@ -533,11 +539,11 @@
|
|||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="784.30382"
|
||||
inkscape:cy="504.96432"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="759.55392"
|
||||
inkscape:cy="1033.5346"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g6709"
|
||||
inkscape:current-layer="g4578"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1037"
|
||||
|
@ -602,7 +608,7 @@
|
|||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
|
@ -703,21 +709,21 @@
|
|||
</g>
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#d45500;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#132d2c;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6763"
|
||||
width="187.85416"
|
||||
height="164.41963"
|
||||
x="320.14584"
|
||||
y="11.249985" />
|
||||
<rect
|
||||
style="fill:#fff6d5;fill-opacity:1;stroke:none;stroke-width:0.79375005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#222222;fill-opacity:1;stroke:none;stroke-width:0.79375005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect6765"
|
||||
width="187.85416"
|
||||
height="121.33036"
|
||||
x="320.14584"
|
||||
y="175.66963" />
|
||||
<rect
|
||||
style="fill:#502d16;fill-opacity:1;stroke:none;stroke-width:0.79375005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#13191a;fill-opacity:1;stroke:none;stroke-width:0.79375005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter9551)"
|
||||
id="rect6767"
|
||||
width="157.61607"
|
||||
height="94.872032"
|
||||
|
@ -763,14 +769,14 @@
|
|||
inkscape:tile-h="43.273309"
|
||||
inkscape:tile-x0="262.01311"
|
||||
inkscape:tile-y0="-56.545273"
|
||||
style="stroke:none">
|
||||
style="stroke:none;filter:url(#filter7193)">
|
||||
<rect
|
||||
y="21.455339"
|
||||
x="334.13098"
|
||||
height="48.75893"
|
||||
width="48.75893"
|
||||
id="rect6785"
|
||||
style="fill:#2b1100;fill-opacity:1;stroke:none;stroke-width:0.79375005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.79375005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
id="g6865"
|
||||
|
@ -951,23 +957,29 @@
|
|||
x="398.43564"
|
||||
y="26.543592"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px">Cargo</tspan></text>
|
||||
<circle
|
||||
style="fill:#cdcdcd;fill-opacity:1;stroke:none;stroke-width:0.79374999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path7854"
|
||||
cx="508"
|
||||
cy="11.249985"
|
||||
r="20.847025" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10.58333397px;line-height:6.61458349px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;word-spacing:0px;fill:#ff000d;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="495.25046"
|
||||
y="23.850527"
|
||||
id="text7862"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan7860"
|
||||
x="495.25046"
|
||||
<g
|
||||
id="g5454"
|
||||
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/character/close.png"
|
||||
inkscape:export-xdpi="91.800003"
|
||||
inkscape:export-ydpi="91.800003">
|
||||
<circle
|
||||
r="20.847025"
|
||||
cy="11.249985"
|
||||
cx="508"
|
||||
id="path7854"
|
||||
style="fill:#3e3e3e;fill-opacity:1;stroke:#c1c1c1;stroke-width:0.79374999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter7193)" />
|
||||
<text
|
||||
id="text7862"
|
||||
y="23.850527"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#ff000d;fill-opacity:1;stroke-width:0.26458335px">X</tspan></text>
|
||||
x="495.25046"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10.58333397px;line-height:6.61458349px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;word-spacing:0px;fill:#ff000d;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter7193)"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#ff000d;fill-opacity:1;stroke-width:0.26458335px"
|
||||
y="23.850527"
|
||||
x="495.25046"
|
||||
id="tspan7860"
|
||||
sodipodi:role="line">X</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
|
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
BIN
out/assets/images/character/cargo-slot.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
out/assets/images/character/close.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
out/assets/images/character/equipment-slot.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
out/assets/images/character/sheet.png
Normal file
After Width: | Height: | Size: 116 KiB |
BIN
out/assets/images/character/ship-selected.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
out/assets/images/character/ship.png
Normal file
After Width: | Height: | Size: 3 KiB |
|
@ -15,6 +15,9 @@ module TS.SpaceTac {
|
|||
// Current battle in which the fleet is engaged (null if not fighting)
|
||||
battle: Battle;
|
||||
|
||||
// Amount of credits available
|
||||
credits = 0;
|
||||
|
||||
// Create a fleet, bound to a player
|
||||
constructor(player: Player = null) {
|
||||
this.player = player || new Player();
|
||||
|
|
|
@ -87,8 +87,11 @@ module TS.SpaceTac {
|
|||
// Priority in play_order
|
||||
play_priority = 0;
|
||||
|
||||
// Upgrade points available
|
||||
upgrade_points = 0;
|
||||
|
||||
// Create a new ship inside a fleet
|
||||
constructor(fleet: Fleet = null, name: string = null) {
|
||||
constructor(fleet: Fleet = null, name = "Ship") {
|
||||
this.fleet = fleet || new Fleet();
|
||||
this.level = 1;
|
||||
this.name = name;
|
||||
|
|
|
@ -77,6 +77,12 @@ module TS.SpaceTac.UI {
|
|||
this.loadImage("map/state-unknown.png");
|
||||
this.loadImage("map/state-enemy.png");
|
||||
this.loadImage("map/state-clear.png");
|
||||
this.loadImage("character/sheet.png");
|
||||
this.loadImage("character/close.png");
|
||||
this.loadImage("character/ship.png");
|
||||
this.loadImage("character/ship-selected.png");
|
||||
this.loadImage("character/cargo-slot.png");
|
||||
this.loadImage("character/equipment-slot.png");
|
||||
|
||||
// Load ships
|
||||
this.loadShip("scout");
|
||||
|
|
|
@ -9,6 +9,7 @@ module TS.SpaceTac.UI.Specs {
|
|||
*/
|
||||
export class TestGame {
|
||||
ui: MainUI;
|
||||
baseview: BaseView;
|
||||
battleview: BattleView;
|
||||
mapview: UniverseMapView;
|
||||
}
|
||||
|
@ -47,6 +48,7 @@ module TS.SpaceTac.UI.Specs {
|
|||
window.requestAnimationFrame(() => ui.destroy());
|
||||
|
||||
testgame.ui = null;
|
||||
testgame.baseview = null;
|
||||
testgame.battleview = null;
|
||||
testgame.mapview = null;
|
||||
});
|
||||
|
@ -58,7 +60,10 @@ module TS.SpaceTac.UI.Specs {
|
|||
* Test setup of an empty BaseView
|
||||
*/
|
||||
export function setupEmptyView(): TestGame {
|
||||
return setupSingleView(testgame => [new BaseView(), []]);
|
||||
return setupSingleView(testgame => {
|
||||
testgame.baseview = new BaseView();
|
||||
return [testgame.baseview, []];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,6 +34,9 @@ module TS.SpaceTac.UI {
|
|||
// Ship tooltip
|
||||
ship_tooltip: ShipTooltip;
|
||||
|
||||
// Character sheet
|
||||
character_sheet: CharacterSheet;
|
||||
|
||||
// Subscription to the battle log
|
||||
log_processor: LogProcessor;
|
||||
|
||||
|
@ -80,6 +83,8 @@ module TS.SpaceTac.UI {
|
|||
this.ship_list = new ShipList(this);
|
||||
this.ship_tooltip = new ShipTooltip(this);
|
||||
this.add.existing(this.ship_tooltip);
|
||||
this.character_sheet = new CharacterSheet(this);
|
||||
this.add.existing(this.character_sheet);
|
||||
|
||||
// "Battle" animation
|
||||
this.displayFightMessage();
|
||||
|
@ -166,6 +171,8 @@ module TS.SpaceTac.UI {
|
|||
cursorClicked(): void {
|
||||
if (this.targetting) {
|
||||
this.targetting.validate();
|
||||
} else if (this.ship_hovered) {
|
||||
this.character_sheet.show(this.ship_hovered);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
29
src/ui/character/CharacterSheet.spec.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
module TS.SpaceTac.UI.Specs {
|
||||
describe("CharacterSheet", function () {
|
||||
let testgame = setupEmptyView();
|
||||
|
||||
it("displays fleet and ship information", function () {
|
||||
let view = testgame.baseview;
|
||||
let sheet = new CharacterSheet(view);
|
||||
|
||||
expect(sheet.x).toEqual(-sheet.width);
|
||||
|
||||
let fleet = new Fleet();
|
||||
let ship1 = fleet.addShip();
|
||||
ship1.name = "Ship 1";
|
||||
let ship2 = fleet.addShip();
|
||||
ship2.name = "Ship 2";
|
||||
|
||||
sheet.show(ship1, false);
|
||||
|
||||
expect(sheet.x).toEqual(0);
|
||||
expect(sheet.portraits.length).toBe(2);
|
||||
expect(sheet.ship_name.text).toEqual("Ship 1");
|
||||
|
||||
let portrait = <Phaser.Button>sheet.portraits.getChildAt(1);
|
||||
portrait.onInputUp.dispatch();
|
||||
|
||||
expect(sheet.ship_name.text).toEqual("Ship 2");
|
||||
});
|
||||
});
|
||||
}
|
152
src/ui/character/CharacterSheet.ts
Normal file
|
@ -0,0 +1,152 @@
|
|||
module TS.SpaceTac.UI {
|
||||
/**
|
||||
* Character sheet, displaying ship characteristics
|
||||
*/
|
||||
export class CharacterSheet extends Phaser.Image {
|
||||
// Currently displayed fleet
|
||||
fleet: Fleet;
|
||||
|
||||
// Currently displayed ship
|
||||
ship: Ship;
|
||||
|
||||
// Ship name
|
||||
ship_name: Phaser.Text;
|
||||
|
||||
// Ship level
|
||||
ship_level: Phaser.Text;
|
||||
|
||||
// Ship upgrade points
|
||||
ship_upgrades: Phaser.Text;
|
||||
|
||||
// Fleet's portraits
|
||||
portraits: Phaser.Group;
|
||||
|
||||
// Credits
|
||||
credits: Phaser.Text;
|
||||
|
||||
// Attributes and skills
|
||||
attributes: { [key: string]: Phaser.Text } = {};
|
||||
|
||||
constructor(view: BaseView) {
|
||||
super(view.game, 0, 0, "character-sheet");
|
||||
|
||||
this.x = -this.width;
|
||||
this.inputEnabled = true;
|
||||
|
||||
let close_button = new Phaser.Button(this.game, view.getWidth(), 0, "character-close", () => this.hide());
|
||||
close_button.anchor.set(1, 0);
|
||||
this.addChild(close_button);
|
||||
|
||||
this.ship_name = new Phaser.Text(this.game, 758, 48, "", { align: "center", font: "30pt Arial", fill: "#FFFFFF" });
|
||||
this.ship_name.anchor.set(0.5, 0.5);
|
||||
this.addChild(this.ship_name);
|
||||
|
||||
this.ship_level = new Phaser.Text(this.game, 552, 1054, "", { align: "center", font: "30pt Arial", fill: "#FFFFFF" });
|
||||
this.ship_level.anchor.set(0.5, 0.5);
|
||||
this.addChild(this.ship_level);
|
||||
|
||||
this.ship_upgrades = new Phaser.Text(this.game, 1066, 1054, "", { align: "center", font: "30pt Arial", fill: "#FFFFFF" });
|
||||
this.ship_upgrades.anchor.set(0.5, 0.5);
|
||||
this.addChild(this.ship_upgrades);
|
||||
|
||||
this.portraits = new Phaser.Group(this.game);
|
||||
this.portraits.position.set(152, 0);
|
||||
this.addChild(this.portraits);
|
||||
|
||||
this.credits = new Phaser.Text(this.game, 136, 38, "", { align: "center", font: "30pt Arial", fill: "#FFFFFF" });
|
||||
this.credits.anchor.set(0.5, 0.5);
|
||||
this.addChild(this.credits);
|
||||
|
||||
let x1 = 664;
|
||||
let x2 = 1066;
|
||||
let y = 662;
|
||||
this.addAttribute(SHIP_ATTRIBUTES.initiative, x1, y);
|
||||
this.addAttribute(SHIP_ATTRIBUTES.hull_capacity, x1, y + 64);
|
||||
this.addAttribute(SHIP_ATTRIBUTES.shield_capacity, x1, y + 128);
|
||||
this.addAttribute(SHIP_ATTRIBUTES.power_capacity, x1, y + 192);
|
||||
this.addAttribute(SHIP_ATTRIBUTES.power_initial, x1, y + 256);
|
||||
this.addAttribute(SHIP_ATTRIBUTES.power_recovery, x1, y + 320);
|
||||
this.addAttribute(SHIP_ATTRIBUTES.skill_material, x2, y);
|
||||
this.addAttribute(SHIP_ATTRIBUTES.skill_electronics, x2, y + 64);
|
||||
this.addAttribute(SHIP_ATTRIBUTES.skill_energy, x2, y + 128);
|
||||
this.addAttribute(SHIP_ATTRIBUTES.skill_human, x2, y + 192);
|
||||
this.addAttribute(SHIP_ATTRIBUTES.skill_gravity, x2, y + 256);
|
||||
this.addAttribute(SHIP_ATTRIBUTES.skill_time, x2, y + 320);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an attribute display
|
||||
*/
|
||||
private addAttribute(attribute: ShipAttribute, x: number, y: number) {
|
||||
let text = new Phaser.Text(this.game, x, y, "", { align: "center", font: "18pt Arial", fill: "#FFFFFF" });
|
||||
text.anchor.set(0.5, 0.5);
|
||||
this.addChild(text);
|
||||
|
||||
this.attributes[attribute.name] = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the fleet sidebar
|
||||
*/
|
||||
updateFleet(fleet: Fleet) {
|
||||
if (fleet != this.fleet) {
|
||||
this.portraits.removeAll(true);
|
||||
this.fleet = fleet;
|
||||
}
|
||||
|
||||
fleet.ships.forEach((ship, idx) => {
|
||||
let portrait = this.portraits.children.length > idx ? this.portraits.getChildAt(idx) : null;
|
||||
let key = ship == this.ship ? "character-ship-selected" : "character-ship";
|
||||
if (portrait instanceof Phaser.Button) {
|
||||
portrait.loadTexture(key);
|
||||
} else {
|
||||
let new_portrait = new Phaser.Button(this.game, 0, idx * 320, key, () => this.show(ship));
|
||||
new_portrait.anchor.set(0.5, 0.5);
|
||||
this.portraits.addChild(new_portrait);
|
||||
|
||||
let portrait_pic = new Phaser.Image(this.game, 0, 0, `ship-${ship.model}-portrait`);
|
||||
portrait_pic.anchor.set(0.5, 0.5);
|
||||
new_portrait.addChild(portrait_pic);
|
||||
}
|
||||
});
|
||||
|
||||
this.credits.setText(fleet.credits.toString());
|
||||
|
||||
this.portraits.scale.set(980 * this.portraits.scale.x / this.portraits.height, 980 * this.portraits.scale.y / this.portraits.height);
|
||||
this.portraits.y = 80 + 160 * this.portraits.scale.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the sheet for a given ship
|
||||
*/
|
||||
show(ship: Ship, animate = true) {
|
||||
this.ship = ship;
|
||||
|
||||
this.ship_name.setText(ship.name);
|
||||
this.ship_level.setText(ship.level.toString());
|
||||
this.ship_upgrades.setText(ship.upgrade_points.toString());
|
||||
|
||||
iteritems(<any>ship.attributes, (key, value: ShipAttribute) => {
|
||||
let text = this.attributes[value.name];
|
||||
if (text) {
|
||||
text.setText(value.get().toString());
|
||||
}
|
||||
});
|
||||
|
||||
this.updateFleet(ship.fleet);
|
||||
|
||||
if (animate) {
|
||||
this.game.tweens.create(this).to({ x: 0 }, 800, Phaser.Easing.Circular.InOut, true);
|
||||
} else {
|
||||
this.x = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the sheet
|
||||
*/
|
||||
hide() {
|
||||
this.game.tweens.create(this).to({ x: -this.width }, 800, Phaser.Easing.Circular.InOut, true);
|
||||
}
|
||||
}
|
||||
}
|