1
0
Fork 0

Add selling and buying from shops

This commit is contained in:
Michaël Lemaire 2017-03-23 19:58:09 +01:00
parent 7f9f13d781
commit 40321621de
22 changed files with 682 additions and 78 deletions

7
TODO
View file

@ -1,7 +1,11 @@
* UI: Use a common component class, and a layer abstraction
* UI: Fix tooltip sometimes being enormous
* Character sheet: add tooltips (on values, slots and equipments)
* Character sheet: add initial character creation
* Character sheet: disable interaction during battle (except for loot screen)
* Character sheet: paginate loot and shop items
* Character sheet: improve eye-catching for shop and loot section
* Shops: add equipment pricing, with usage depreciation
* Add battle statistics and/or critics in outcome dialog
* Add battle experience and feedback on level up
* Ensure that tweens and particle emitters get destroyed once animation is done (or view changes)
@ -32,10 +36,9 @@
* TacticalAI: allow to play several moves in the same turn
* TacticalAI: add pauses to not play too quickly
* TacticalAI: replace BullyAI
* Add retreat from battle
* Map: restore fog of war
* Map: add information on current star/location + information on hovered location
* Map: add stores and shipyards
* Map: add shops and shipyards
* Map: remove jump links that cross the radius of other systems
* Map: disable interaction (zoom, selection) while moving/jumping
* Menu: fix background stars aggregating at right side when the game is not focused

View file

@ -16,7 +16,7 @@
viewBox="0 0 507.99999 285.75001"
version="1.1"
id="svg8"
inkscape:version="0.92.0 r15299"
inkscape:version="0.92.1 r15371"
sodipodi:docname="character.svg"
enable-background="new"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/character/close.png"
@ -24,6 +24,22 @@
inkscape:export-ydpi="96">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient4790">
<stop
style="stop-color:#12384b;stop-opacity:1"
offset="0"
id="stop4786" />
<stop
id="stop4857"
offset="0.11033978"
style="stop-color:#163e4e;stop-opacity:1" />
<stop
style="stop-color:#132d2c;stop-opacity:1"
offset="1"
id="stop4788" />
</linearGradient>
<inkscape:tag
id="Définir 3"
inkscape:label="Dynamic"
@ -531,6 +547,24 @@
result="composite2"
id="feComposite9549" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4790"
id="linearGradient4792"
x1="361.74548"
y1="225.52148"
x2="361.74548"
y2="214.65468"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.3217523,0,0,0.7757954,-116.27518,50.583778)" />
<filter
style="color-interpolation-filters:sRGB"
id="filter4837"
inkscape:label="Color price">
<feColorMatrix
id="feColorMatrix4839"
values="1 0 0 0 0 0 1 0 0 0 0 0 0.8 0 0 0 0 0 1 0 " />
</filter>
</defs>
<sodipodi:namedview
id="base"
@ -540,10 +574,10 @@
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="759.55392"
inkscape:cy="1033.5346"
inkscape:cx="1214.6427"
inkscape:cy="366.58654"
inkscape:document-units="px"
inkscape:current-layer="g4578"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1037"
@ -729,37 +763,6 @@
height="94.872032"
x="334.8869"
y="188.52083" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333397px;line-height:10.60999966px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="343.32455"
y="204.7738"
id="text6771"><tspan
sodipodi:role="line"
id="tspan6769"
x="343.32455"
y="204.7738"
style="line-height:10.60999966px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px">Artana come from a long </tspan><tspan
sodipodi:role="line"
x="343.32455"
y="215.3838"
style="line-height:10.60999966px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px"
id="tspan6775">line of merchants, and has</tspan><tspan
sodipodi:role="line"
x="343.32455"
y="225.9938"
style="line-height:10.60999966px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px"
id="tspan6779">always valued peace over</tspan><tspan
sodipodi:role="line"
x="343.32455"
y="236.60381"
style="line-height:10.60999966px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px"
id="tspan6781">brutality. She is pragmatic</tspan><tspan
sodipodi:role="line"
x="343.32455"
y="247.21381"
style="line-height:10.60999966px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px"
id="tspan6783">and resolute.</tspan></text>
<g
id="g6788"
transform="matrix(0.88749508,0,0,0.88749508,157.8634,-65.47073)"
@ -981,6 +984,128 @@
sodipodi:role="line">X</tspan></text>
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="ModeArea"
style="display:inline">
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="DescriptionMode"
style="display:none">
<text
transform="translate(0,-11.249983)"
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333397px;line-height:10.60999966px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="343.32455"
y="204.7738"
id="text6771"><tspan
sodipodi:role="line"
id="tspan6769"
x="343.32455"
y="204.7738"
style="line-height:10.60999966px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px">Artana come from a long </tspan><tspan
sodipodi:role="line"
x="343.32455"
y="215.3838"
style="line-height:10.60999966px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px"
id="tspan6775">line of merchants, and has</tspan><tspan
sodipodi:role="line"
x="343.32455"
y="225.9938"
style="line-height:10.60999966px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px"
id="tspan6779">always valued peace over</tspan><tspan
sodipodi:role="line"
x="343.32455"
y="236.60381"
style="line-height:10.60999966px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px"
id="tspan6781">brutality. She is pragmatic</tspan><tspan
sodipodi:role="line"
x="343.32455"
y="247.21381"
style="line-height:10.60999966px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px"
id="tspan6783">and resolute.</tspan></text>
</g>
<g
inkscape:groupmode="layer"
id="layer7"
inkscape:label="LootShopMode"
style="display:inline">
<use
style="display:inline;opacity:1"
x="0"
y="0"
xlink:href="#g6788"
id="use4760"
width="100%"
height="100%"
transform="translate(-114.65862,228.75806)" />
<flowRoot
xml:space="preserve"
id="flowRoot4763"
style="font-style:normal;font-weight:normal;font-size:26.66666603px;line-height:25px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
transform="scale(0.26458334)"><flowRegion
id="flowRegion4765"><rect
id="rect4767"
width="163.64471"
height="46.467018"
x="1491.9952"
y="626.44153" /></flowRegion><flowPara
id="flowPara4769" /></flowRoot> <text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:7.05555534px;line-height:6.61458349px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="387.81439"
y="173.22652"
id="text4773"><tspan
sodipodi:role="line"
id="tspan4771"
x="387.81439"
y="173.22652"
style="fill:#ffffff;stroke-width:0.26458335px">Lootable items</tspan></text>
<path
sodipodi:type="star"
style="fill:#267482;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter9551)"
id="path4777"
sodipodi:sides="3"
sodipodi:cx="498.1904"
sodipodi:cy="225.08482"
sodipodi:r1="5.7819242"
sodipodi:r2="2.8909619"
sodipodi:arg1="2.0943951"
sodipodi:arg2="3.1415927"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 495.29944,230.09212 0,-10.01459 8.67288,5.00729 z"
inkscape:transform-center-x="-1.4454898"
inkscape:transform-center-y="3.6247564e-06"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/character/scroll.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<use
x="0"
y="0"
xlink:href="#path4777"
inkscape:transform-center-x="1.4454903"
inkscape:transform-center-y="3.6247564e-06"
id="use4779"
width="100%"
height="100%"
transform="matrix(-1,0,0,1,827.29259,0)" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:7.05555534px;line-height:6.61458349px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter4837)"
x="393.87433"
y="281.21716"
id="text4851"><tspan
sodipodi:role="line"
id="tspan4849"
x="393.87433"
y="281.21716"
style="fill:#ffffff;stroke-width:0.26458335px">Sell for 150</tspan></text>
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer2"
@ -1508,37 +1633,40 @@
d="M 9.0381146,10.399538 H 81.83353 l -2.840798,19.654759 h -67.11382 z"
style="fill:none;fill-opacity:1;stroke:#b2c0e1;stroke-width:0.25165766;stroke-opacity:0.5362319;filter:url(#filter4674)" />
<g
id="g6698"
transform="translate(0,-0.44954215)"
id="g4822"
style="filter:url(#filter7193)">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12.69999981px;line-height:6.61458349px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="63.311016"
y="25.424089"
id="text4555"><tspan
sodipodi:role="line"
id="tspan4553"
x="63.311016"
y="25.424089"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.69999981px;line-height:7.61000013px;font-family:'DejaVu Serif';-inkscape-font-specification:'DejaVu Serif';fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px">Z</tspan></text>
<g
id="g4563"
transform="translate(0.74045089,0.09552719)">
<path
inkscape:connector-curvature="0"
id="path4557"
d="M 65.917707,13.659581 V 27.691947"
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.82559979;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<use
style="stroke-width:1.39999998;stroke-miterlimit:4;stroke-dasharray:none"
height="100%"
width="100%"
transform="translate(2.0039452,0.04724702)"
id="use4559"
xlink:href="#path4557"
y="0"
x="0" />
transform="translate(0,-0.44954215)"
id="g6698">
<text
id="text4555"
y="25.424089"
x="63.311016"
style="font-style:normal;font-weight:normal;font-size:12.69999981px;line-height:6.61458349px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.69999981px;line-height:7.61000013px;font-family:'DejaVu Serif';-inkscape-font-specification:'DejaVu Serif';fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px"
y="25.424089"
x="63.311016"
id="tspan4553"
sodipodi:role="line">Z</tspan></text>
<g
transform="translate(0.74045089,0.09552719)"
id="g4563">
<path
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.82559979;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 65.917707,13.659581 V 27.691947"
id="path4557"
inkscape:connector-curvature="0" />
<use
x="0"
y="0"
xlink:href="#path4557"
id="use4559"
transform="translate(2.0039452,0.04724702)"
width="100%"
height="100%"
style="stroke-width:1.39999998;stroke-miterlimit:4;stroke-dasharray:none" />
</g>
</g>
</g>
<ellipse
@ -1567,5 +1695,46 @@
sodipodi:nodetypes="ccccc"
transform="matrix(0.98778177,0,0,0.99708999,0.55514569,0.70711812)" />
</g>
<g
id="g4782"
transform="translate(0,-34.783275)">
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="rect4783"
d="m 347.46864,217.11316 h 27.82481 l 2.77932,8.5012 h -33.38345 z"
style="display:inline;fill:url(#linearGradient4792);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26792371px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter9551)"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/character/price-tag.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
transform="matrix(1,0,0,-1,0,442.72752)" />
<g
id="g4835"
style="display:inline;filter:url(#filter4837)">
<text
id="text4796"
y="223.86424"
x="352.19824"
style="font-style:normal;font-weight:normal;font-size:6.3499999px;line-height:6.61458349px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-size:6.3499999px;fill:#ffffff;fill-opacity:1;stroke-width:0.26458335px"
y="223.86424"
x="352.19824"
id="tspan4794"
sodipodi:role="line">150</tspan></text>
</g>
<use
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
inkscape:export-filename="/home/michael/workspace/perso/spacetac/out/assets/images/character/price-tag.png"
transform="matrix(0.53713219,0,0,0.53713219,334.59484,210.43561)"
height="100%"
width="100%"
id="use4826"
xlink:href="#g6698"
y="0"
x="0"
style="display:inline;opacity:1;filter:url(#filter4837)" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View file

@ -338,9 +338,9 @@
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="2.8284271"
inkscape:cx="1297.0192"
inkscape:cy="519.42484"
inkscape:zoom="16"
inkscape:cx="992.35147"
inkscape:cy="383.97616"
inkscape:document-units="mm"
inkscape:current-layer="layer5"
showgrid="false"
@ -674,6 +674,59 @@
id="rect4920"
style="fill:none;fill-rule:evenodd;stroke:#cdd8e2;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.76382979" />
</g>
<use
x="0"
y="0"
xlink:href="#g7279"
id="use4605"
width="100%"
height="100%"
transform="rotate(135.15474,292.10549,143.11838)" />
<g
id="g5470">
<use
transform="translate(-85.829914,36.267568)"
height="100%"
width="100%"
id="use4607"
xlink:href="#path7269"
y="0"
x="0" />
<g
style="display:inline;opacity:1"
transform="matrix(0.42197579,0,0,0.42197579,234.85646,177.55249)"
id="g6698">
<text
id="text4555"
y="25.424089"
x="63.311016"
style="font-style:normal;font-weight:normal;font-size:12.69999981px;line-height:6.61458349px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#1951ae;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.69999981px;line-height:7.61000013px;font-family:'DejaVu Serif';-inkscape-font-specification:'DejaVu Serif';fill:#1951ae;fill-opacity:1;stroke-width:0.26458335px;"
y="25.424089"
x="63.311016"
id="tspan4553"
sodipodi:role="line">Z</tspan></text>
<g
transform="translate(0.74045089,0.09552719)"
id="g4563">
<path
style="fill:none;fill-rule:evenodd;stroke:#1951ae;stroke-width:0.82559979;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 65.917707,13.659581 V 27.691947"
id="path4557"
inkscape:connector-curvature="0" />
<use
x="0"
y="0"
xlink:href="#path4557"
id="use4559"
transform="translate(2.0039452,0.04724702)"
width="100%"
height="100%"
style="stroke-width:1.39999998;stroke-miterlimit:4;stroke-dasharray:none" />
</g>
</g>
</g>
</g>
<g
inkscape:groupmode="layer"

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -70,5 +70,21 @@ module TS.SpaceTac.Specs {
expect(location2.encounter).not.toBeNull();
expect(spyloot).toHaveBeenCalledTimes(0);
});
it("generates a new campaign", function () {
let session = new GameSession();
session.startNewGame();
expect(session.player).not.toBeNull();
expect(session.player.fleet.ships.length).toBe(3);
expect(session.player.fleet.credits).toBe(500);
expect(session.player.universe.stars.length).toBe(50);
expect(session.getBattle()).toBeNull();
let start_location = nn(session.player.fleet.location);
expect(start_location.shop).not.toBeNull();
expect(nn(start_location.shop).stock.length).toBe(50);
expect(start_location.encounter).toBeNull();
expect(start_location.encounter_gen).toBe(true);
});
});
}

View file

@ -24,7 +24,7 @@ module TS.SpaceTac {
return serializer.serialize(this);
}
// Generate a real single player game
// Generate a real single player game (campaign)
startNewGame(): void {
var fleet_generator = new FleetGenerator();
@ -32,12 +32,13 @@ module TS.SpaceTac {
this.universe.generate();
var start_location = this.universe.stars[0].locations[0];
start_location.encounter_gen = true;
start_location.encounter = null;
start_location.clearEncounter();
start_location.addShop(50);
this.player = new Player(this.universe);
this.player.fleet = fleet_generator.generate(1, this.player);
this.player.fleet.setLocation(start_location);
this.player.fleet.credits = 500;
}
// Start a new "quick battle" game

35
src/core/Shop.spec.ts Normal file
View file

@ -0,0 +1,35 @@
module TS.SpaceTac.Specs {
describe("Shop", () => {
it("generates a stock", () => {
let shop = new Shop();
expect(shop.stock.length).toBe(0);
shop.generateStock(8);
expect(shop.stock.length).toBe(8);
});
it("buys and sells items", function () {
let shop = new Shop();
let equ1 = new Equipment(SlotType.Shield, "shield");
let equ2 = new Equipment(SlotType.Hull, "hull");
shop.stock = [equ1, equ2];
let fleet = new Fleet();
fleet.credits = 1000;
spyOn(shop, "getPrice").and.returnValue(800);
let result = shop.sellToFleet(equ1, fleet);
expect(result).toBe(true);
expect(shop.stock).toEqual([equ2]);
expect(fleet.credits).toEqual(200);
result = shop.sellToFleet(equ2, fleet);
expect(result).toBe(false);
expect(shop.stock).toEqual([equ2]);
expect(fleet.credits).toEqual(200);
result = shop.buyFromFleet(equ1, fleet);
expect(result).toBe(true);
expect(shop.stock).toEqual([equ2, equ1]);
expect(fleet.credits).toEqual(1000);
});
});
}

68
src/core/Shop.ts Normal file
View file

@ -0,0 +1,68 @@
module TS.SpaceTac {
/**
* A shop is a place to buy/sell equipments
*/
export class Shop {
// Equipment in stock
stock: Equipment[] = [];
/**
* Generate a random stock
*/
generateStock(items: number) {
let generator = new LootGenerator();
this.stock = nna(range(items).map(i => generator.generate()));
this.sortStock();
}
/**
* Sort the stock by equipment level, then by value
*/
sortStock() {
// TODO
}
/**
* Get the buy/sell price for an equipment
*/
getPrice(equipment: Equipment): number {
// TODO
return 100;
}
/**
* A fleet buys an item
*
* This does not put the item anywhere on the fleet, only remove the item from stock, and make the payment
*/
sellToFleet(equipment: Equipment, fleet: Fleet) {
let price = this.getPrice(equipment);
if (price <= fleet.credits) {
if (remove(this.stock, equipment)) {
fleet.credits -= price;
return true;
} else {
return false;
}
} else {
return false;
}
}
/**
* A fleet sells an item
*
* This does not check if the item is anywhere on the fleet, only add the item to the shop stock, and make the payment
*/
buyFromFleet(equipment: Equipment, fleet: Fleet) {
let price = this.getPrice(equipment);
if (add(this.stock, equipment)) {
fleet.credits += price;
return true;
} else {
return false;
}
}
}
}

View file

@ -31,6 +31,9 @@ module TS.SpaceTac {
encounter_gen = false;
encounter_random = RandomGenerator.global;
// Shop to buy/sell equipment
shop: Shop | null = null;
constructor(star = new Star(), type: StarLocationType = StarLocationType.PLANET, x: number = 0, y: number = 0) {
this.star = star;
this.type = type;
@ -41,6 +44,16 @@ module TS.SpaceTac {
this.jump_dest = null;
}
/**
* Add a shop in this location
*/
addShop(generate_items = 0) {
this.shop = new Shop();
if (generate_items) {
this.shop.generateStock(generate_items);
}
}
// Set the jump destination of a WARP location
setJumpDestination(jump_dest: StarLocation): void {
if (this.type === StarLocationType.WARP) {
@ -99,6 +112,7 @@ module TS.SpaceTac {
* Clear an encounter, when the encountered fleet has been defeated
*/
clearEncounter() {
this.encounter_gen = true;
this.encounter = null;
}
}

View file

@ -89,6 +89,7 @@ module TS.SpaceTac.UI {
this.loadImage("map/state-unknown.png");
this.loadImage("map/state-enemy.png");
this.loadImage("map/state-clear.png");
this.loadImage("map/state-shop.png");
this.loadImage("character/sheet.png");
this.loadImage("character/close.png");
this.loadImage("character/ship.png");
@ -101,6 +102,8 @@ module TS.SpaceTac.UI {
this.loadImage("character/slot-shield.png");
this.loadImage("character/slot-engine.png");
this.loadImage("character/slot-weapon.png");
this.loadImage("character/price-tag.png");
this.loadImage("character/scroll.png");
this.loadImage("equipment/ironhull.png");
this.loadImage("equipment/basicforcefield.png");
this.loadImage("equipment/basicpowercore.png");

View file

@ -26,6 +26,9 @@ module TS.SpaceTac.UI {
scale: this.scale.x
}
}
getPriceOffset(): number {
return 82;
}
addEquipment(equipment: CharacterEquipment, source: CharacterEquipmentContainer | null, test: boolean): boolean {
if (this.sheet.ship.getFreeCargoSpace() > 0) {
if (test) {

View file

@ -11,6 +11,10 @@ module TS.SpaceTac.UI {
* Get a centric anchor point and scaling to snap the equipment
*/
getEquipmentAnchor(): { x: number, y: number, scale: number }
/**
* Get a vertical offset to position the price tag
*/
getPriceOffset(): number
/**
* Add an equipment to the container
*/
@ -29,6 +33,7 @@ module TS.SpaceTac.UI {
item: Equipment
container: CharacterEquipmentContainer
tooltip: string
price: number
constructor(sheet: CharacterSheet, equipment: Equipment, container: CharacterEquipmentContainer) {
let icon = sheet.game.cache.checkImageKey(`equipment-${equipment.code}`) ? `equipment-${equipment.code}` : `battle-actions-${equipment.action.code}`;
@ -38,6 +43,7 @@ module TS.SpaceTac.UI {
this.item = equipment;
this.container = container;
this.tooltip = equipment.name;
this.price = 0;
this.container.addEquipment(this, null, false);
@ -57,6 +63,28 @@ module TS.SpaceTac.UI {
return ifirst(this.sheet.iEquipmentContainers(), container => container.isInside(x, y));
}
/**
* Display a price tag
*/
setPrice(price: number) {
if (!price || this.price) {
return;
}
this.price = price;
let tag = new Phaser.Image(this.game, 0, 0, "character-price-tag");
let yoffset = this.container.getPriceOffset();
tag.position.set(0, -yoffset * 2 + tag.height);
tag.anchor.set(0.5, 0.5);
tag.scale.set(2, 2);
tag.alpha = 0.85;
this.addChild(tag);
let text = new Phaser.Text(this.game, -10, 4, price.toString(), { align: "center", font: "18pt Arial", fill: "#FFFFCC" });
text.anchor.set(0.5, 0.5);
tag.addChild(text);
}
/**
* Snap in place to its current container
*/

View file

@ -39,6 +39,9 @@ module TS.SpaceTac.UI {
// not needed, equipment is never shown snapped in the slot
return { x: 0, y: 0, scale: 1 };
}
getPriceOffset(): number {
return 0;
}
addEquipment(equipment: CharacterEquipment, source: CharacterEquipmentContainer | null, test: boolean): boolean {
if (this.ship != this.sheet.ship && equipment.item.slot !== null) {
let slot = this.ship.getFreeSlot(equipment.item.slot);

View file

@ -0,0 +1,43 @@
module TS.SpaceTac.UI.Specs {
describe("CharacterLootSlot", function () {
let testgame = setupEmptyView();
it("takes or discard loot", function () {
let view = testgame.baseview;
let sheet = new CharacterSheet(view);
let fleet = new Fleet();
let ship = fleet.addShip();
ship.setCargoSpace(2);
let equ1 = new Equipment(SlotType.Shield, "equ1");
ship.addCargo(equ1)
let equ2 = new Equipment(SlotType.Weapon, "equ2");
let loot = [equ2];
sheet.setLoot(loot);
sheet.show(ship);
expect(ship.cargo).toEqual([equ1]);
expect(loot).toEqual([equ2]);
let cargo_slot = <CharacterCargo>sheet.ship_cargo.children[0];
expect(cargo_slot instanceof CharacterCargo).toBe(true);
let loot_slot = <CharacterLootSlot>sheet.loot_slots.children[0];
expect(loot_slot instanceof CharacterLootSlot).toBe(true);
// loot to cargo
let equ2s = <CharacterEquipment>sheet.equipments.children[1];
expect(equ2s.item).toBe(equ2);
equ2s.applyDragDrop(loot_slot, cargo_slot, false);
expect(ship.cargo).toEqual([equ1, equ2]);
expect(loot).toEqual([]);
// discard to cargo
let equ1s = <CharacterEquipment>sheet.equipments.children[0];
expect(equ1s.item).toBe(equ1);
equ1s.applyDragDrop(cargo_slot, loot_slot, false);
expect(ship.cargo).toEqual([equ2]);
expect(loot).toEqual([equ1]);
});
});
}

View file

@ -1,3 +1,4 @@
/// <reference path="CharacterCargo.ts" />
/// <reference path="CharacterEquipment.ts" />
module TS.SpaceTac.UI {

View file

@ -37,10 +37,16 @@ module TS.SpaceTac.UI {
// Ship cargo
ship_cargo: Phaser.Group;
// Mode title
mode_title: Phaser.Text;
// Loot items
loot_slots: Phaser.Group;
loot_items: Equipment[] = [];
// Shop
shop: Shop | null = null;
// Fleet's portraits
portraits: Phaser.Group;
@ -107,6 +113,10 @@ module TS.SpaceTac.UI {
this.equipments = new Phaser.Group(this.game);
this.addChild(this.equipments);
this.mode_title = new Phaser.Text(this.game, 1548, 648, "", { align: "center", font: "18pt Arial", fill: "#FFFFFF" });
this.mode_title.anchor.set(0.5, 0.5);
this.addChild(this.mode_title);
let x1 = 664;
let x2 = 1066;
let y = 662;
@ -222,6 +232,10 @@ module TS.SpaceTac.UI {
this.updateFleet(ship.fleet);
if (this.shop) {
this.updatePrices(this.shop);
}
if (animate) {
this.game.tweens.create(this).to({ x: this.xshown }, 800, Phaser.Easing.Circular.InOut, true);
} else {
@ -233,7 +247,10 @@ module TS.SpaceTac.UI {
* Hide the sheet
*/
hide(animate = true) {
this.loot_items = [];
this.shop = null;
this.loot_slots.visible = false;
this.mode_title.visible = false;
this.portraits.children.forEach((portrait: Phaser.Button) => portrait.loadTexture("character-ship"));
@ -248,11 +265,39 @@ module TS.SpaceTac.UI {
* Set the list of lootable equipment
*
* The list of equipments may be altered if items are taken from it
*
* This list will be shown until sheet is closed
*/
setLoot(loot: Equipment[]) {
this.loot_items = loot;
this.updateLoot();
this.loot_slots.visible = true;
this.mode_title.setText("Lootable items");
this.mode_title.visible = true;
}
/**
* Set the displayed shop
*
* This shop will be shown until sheet is closed
*/
setShop(shop: Shop) {
this.shop = shop;
this.updateLoot();
this.loot_slots.visible = true;
this.mode_title.setText("Shop's equipment");
this.mode_title.visible = true;
}
/**
* Update the price tags on each equipment, for a specific shop
*/
updatePrices(shop: Shop) {
this.equipments.children.forEach((equipement: CharacterEquipment) => {
equipement.setPrice(shop.getPrice(equipement.item));
});
}
/**
@ -263,13 +308,16 @@ module TS.SpaceTac.UI {
let info = CharacterSheet.getSlotPositions(12, 588, 354, 196, 196);
range(12).forEach(idx => {
let loot_slot = new CharacterLootSlot(this, info.positions[idx].x, info.positions[idx].y);
let loot_slot = this.shop ? new CharacterShopSlot(this, info.positions[idx].x, info.positions[idx].y) : new CharacterLootSlot(this, info.positions[idx].x, info.positions[idx].y);
loot_slot.scale.set(info.scaling, info.scaling);
this.loot_slots.addChild(loot_slot);
if (idx < this.loot_items.length) {
let equipment = new CharacterEquipment(this, this.loot_items[idx], loot_slot);
this.equipments.addChild(equipment);
} else if (this.shop && idx < this.shop.stock.length) {
let equipment = new CharacterEquipment(this, this.shop.stock[idx], loot_slot);
this.equipments.addChild(equipment);
}
});
}

View file

@ -0,0 +1,57 @@
module TS.SpaceTac.UI.Specs {
describe("CharacterShopSlot", function () {
let testgame = setupEmptyView();
it("buys and sell if bound to a shop", function () {
let view = testgame.baseview;
let sheet = new CharacterSheet(view);
let fleet = new Fleet();
fleet.credits = 100;
let ship = fleet.addShip();
ship.setCargoSpace(2);
let equ1 = new Equipment(SlotType.Shield, "equ1");
ship.addCargo(equ1)
let equ2 = new Equipment(SlotType.Weapon, "equ2");
let shop = new Shop();
shop.stock = [equ2];
spyOn(shop, "getPrice").and.returnValue(120);
sheet.setShop(shop);
sheet.show(ship);
expect(ship.cargo).toEqual([equ1]);
expect(shop.stock).toEqual([equ2]);
expect(fleet.credits).toBe(100);
let cargo_slot = <CharacterCargo>sheet.ship_cargo.children[0];
expect(cargo_slot instanceof CharacterCargo).toBe(true);
let shop_slot = <CharacterShopSlot>sheet.loot_slots.children[0];
expect(shop_slot instanceof CharacterShopSlot).toBe(true);
// sell
let equ1s = <CharacterEquipment>sheet.equipments.children[0];
expect(equ1s.item).toBe(equ1);
equ1s.applyDragDrop(cargo_slot, shop_slot, false);
expect(ship.cargo).toEqual([]);
expect(shop.stock).toEqual([equ2, equ1]);
expect(fleet.credits).toBe(220);
// buy
let equ2s = <CharacterEquipment>sheet.equipments.children[1];
expect(equ2s.item).toBe(equ2);
equ2s.applyDragDrop(shop_slot, cargo_slot, false);
expect(ship.cargo).toEqual([equ2]);
expect(shop.stock).toEqual([equ1]);
expect(fleet.credits).toBe(100);
// not enough money
equ1s = <CharacterEquipment>sheet.equipments.children[0];
expect(equ1s.item).toBe(equ1);
equ1s.applyDragDrop(shop_slot, cargo_slot, false);
expect(ship.cargo).toEqual([equ2]);
expect(shop.stock).toEqual([equ1]);
expect(fleet.credits).toBe(100);
});
});
}

View file

@ -0,0 +1,35 @@
/// <reference path="CharacterLootSlot.ts" />
module TS.SpaceTac.UI {
/**
* Display a shop slot
*/
export class CharacterShopSlot extends CharacterLootSlot {
addEquipment(equipment: CharacterEquipment, source: CharacterEquipmentContainer | null, test: boolean): boolean {
let shop = this.sheet.shop;
if (shop && !contains(shop.stock, equipment.item)) {
if (test) {
return true;
} else {
return shop.buyFromFleet(equipment.item, this.sheet.fleet);
}
} else {
return false;
}
}
removeEquipment(equipment: CharacterEquipment, destination: CharacterEquipmentContainer | null, test: boolean): boolean {
let shop = this.sheet.shop;
if (shop && contains(shop.stock, equipment.item)) {
let price = shop.getPrice(equipment.item);
if (test) {
return price <= this.sheet.fleet.credits;
} else {
return shop.sellToFleet(equipment.item, this.sheet.fleet);
}
} else {
return false;
}
}
}
}

View file

@ -32,6 +32,9 @@ module TS.SpaceTac.UI {
scale: this.scale.x
}
}
getPriceOffset(): number {
return 66;
}
addEquipment(equipment: CharacterEquipment, source: CharacterEquipmentContainer | null, test: boolean): boolean {
if (equipment.item.slot !== null && this.sheet.ship.getFreeSlot(equipment.item.slot)) {
if (test) {

View file

@ -30,7 +30,16 @@ module TS.SpaceTac.UI {
// Show locations
starsystem.locations.map(location => {
let location_sprite: Phaser.Image | null = null;
let fleet_move = () => this.fleet_display.moveToLocation(location);
let fleet_move = () => {
if (location == this.player.fleet.location) {
if (location.shop) {
this.view.character_sheet.setShop(location.shop);
this.view.character_sheet.show(this.player.fleet.ships[0]);
}
} else {
this.fleet_display.moveToLocation(location);
}
}
if (location.type == StarLocationType.STAR) {
location_sprite = this.addImage(location.x, location.y, "map-location-star", fleet_move);
@ -43,13 +52,15 @@ module TS.SpaceTac.UI {
}
this.view.tooltip.bindDynamicText(<Phaser.Button>location_sprite, () => {
let visited = this.player.hasVisitedLocation(location);
let shop = (visited && !location.encounter && location.shop) ? " (shop present)" : "";
if (location == this.player.fleet.location) {
return "Current fleet location";
return `Current fleet location${shop}`;
} else {
let visited = this.player.hasVisitedLocation(location);
let loctype = StarLocationType[location.type].toLowerCase();
let danger = (visited && location.encounter) ? " [enemy fleet detected !]" : "";
return `${visited ? "Visited" : "Unvisited"} ${loctype} - Move the fleet there${danger}`;
return `${visited ? "Visited" : "Unvisited"} ${loctype} - Move the fleet there${danger}${shop}`;
}
});
@ -82,7 +93,17 @@ module TS.SpaceTac.UI {
* Return the sprite code to use for visited status.
*/
getVisitedKey(location: StarLocation) {
return this.player.hasVisitedLocation(location) ? (location.encounter ? "map-state-enemy" : "map-state-clear") : "map-state-unknown";
if (this.player.hasVisitedLocation(location)) {
if (location.encounter) {
return "map-state-enemy";
} else if (location.shop) {
return "map-state-shop";
} else {
return "map-state-clear";
}
} else {
return "map-state-unknown";
}
}
/**